def CheckAccurate(self): """ Make sure constraints are consistent and residual is satisfactory """ scale_factor = norms.norm_infty(self.Proj.x[:self.n]) if self.b is not None: scale_factor = max(scale_factor, norms.norm_infty(self.b)) max_res = max(1.0e-6 * scale_factor, self.abstol) res = norms.norm_infty(self.Proj.residual) if res > max_res: if self.Proj.isFullRank: self._write(' Large residual. ' + 'Factorization likely inaccurate...\n') else: self._write(' Large residual. ' + 'Constraints likely inconsistent...\n') if self.debug: self._write(' accurate to within %8.1e...\n' % res) return
def SolveSystem(A, rhs, itref_threshold=1.0e-6, nitrefmax=5, **kwargs): # Obtain Sils context object t = cputime() LBL = LBLContext(A, **kwargs) t_analyze = cputime() - t # Solve system and compute residual t = cputime() LBL.solve(rhs) t_solve = cputime() - t_analyze # Compute residual norm nrhsp1 = norms.norm_infty(rhs) + 1 nr = norms.norm2(LBL.residual)/nrhsp1 # If residual not small, perform iterative refinement LBL.refine(rhs, tol = itref_threshold, nitref=nitrefmax) nr1 = norms.norm_infty(LBL.residual)/nrhsp1 return (LBL.x, LBL.residual, nr, nr1, t_analyze, t_solve, LBL.neig)
# Call projected CG to solve problem # min <g,d> + 1/2 <d, Hd> # s.t Jd = 0, |d| <= delta CG = Ppcg(g, SimpleLinearOperator(nlp.n, nlp.n, lambda p: nlp.hprod(nlp.x0, nlp.pi0, p), symmetric=True), A=J, radius=delta, debug=True) #CG = Ppcg(g, A=J, rhs=-c, matvec=hprod, debug=True) CG.Solve() Jd = numpy.empty(nlp.m, 'd') J.matvec(CG.x, Jd) feas = norms.norm_infty(Jd) #feas = norms.norm_infty( c + Jd ) # for nonzero rhs constraint snorm = norms.norm2(CG.x) # The trust-region is in the l2 norm sys.stdout.write('Number of variables : %-d\n' % nlp.n) sys.stdout.write('Number of constraints : %-d\n' % nlp.m) sys.stdout.write('Converged : %-s\n' % repr(CG.converged)) sys.stdout.write('Trust-region radius : %8.1e\n' % delta) sys.stdout.write('Solution norm : %8.1e\n' % snorm) sys.stdout.write('Final/Initial Residual : ') sys.stdout.write('%8.1e / %8.1e\n' % (CG.residNorm, CG.residNorm0)) sys.stdout.write('Feasibility error : %8.1e\n' % feas) sys.stdout.write('Number of iterations : %-d\n' % CG.iter)
# J.matvec(e, c) # Call projected CG to solve problem # min <g,d> + 1/2 <d, Hd> # s.t Jd = 0, |d| <= delta CG = Ppcg( g, SimpleLinearOperator(nlp.n, nlp.n, lambda p: nlp.hprod(nlp.x0, nlp.pi0, p), symmetric=True), A=J, radius=delta, debug=True, ) # CG = Ppcg(g, A=J, rhs=-c, matvec=hprod, debug=True) CG.Solve() Jd = numpy.empty(nlp.m, "d") J.matvec(CG.x, Jd) feas = norms.norm_infty(Jd) # feas = norms.norm_infty( c + Jd ) # for nonzero rhs constraint snorm = norms.norm2(CG.x) # The trust-region is in the l2 norm sys.stdout.write("Number of variables : %-d\n" % nlp.n) sys.stdout.write("Number of constraints : %-d\n" % nlp.m) sys.stdout.write("Converged : %-s\n" % repr(CG.converged)) sys.stdout.write("Trust-region radius : %8.1e\n" % delta) sys.stdout.write("Solution norm : %8.1e\n" % snorm) sys.stdout.write("Final/Initial Residual : ") sys.stdout.write("%8.1e / %8.1e\n" % (CG.residNorm, CG.residNorm0)) sys.stdout.write("Feasibility error : %8.1e\n" % feas) sys.stdout.write("Number of iterations : %-d\n" % CG.iter)
def solve(self, **kwargs): """ Solve the input problem with the primal-dual-regularized interior-point method. Accepted input keyword arguments are :keywords: :itermax: The maximum allowed number of iterations (default: 10n) :tolerance: Stopping tolerance (default: 1.0e-6) :PredictorCorrector: Use the predictor-corrector method (default: `True`). If set to `False`, a variant of the long-step method is used. The long-step method is generally slower and less robust. Upon exit, the following members of the class instance are set: * x..............final iterate * y..............final value of the Lagrange multipliers associated to A1 x + A2 s = b * z..............final value of the Lagrange multipliers associated to s>=0 * obj_value......final cost * iter...........total number of iterations * kktResid.......final relative residual * solve_time.....time to solve the QP * status.........string describing the exit status. * short_status...short version of status, used for printing. """ qp = self.qp itermax = kwargs.get('itermax', max(100, 10 * qp.n)) tolerance = kwargs.get('tolerance', 1.0e-6) PredictorCorrector = kwargs.get('PredictorCorrector', True) check_infeasible = kwargs.get('check_infeasible', True) # Transfer pointers for convenience. m, n = self.A.shape on = qp.original_n A = self.A b = self.b c = self.c Q = self.Q diagQ = self.diagQ H = self.H regpr = self.regpr regdu = self.regdu regpr_min = self.regpr_min regdu_min = self.regdu_min # Obtain initial point from Mehrotra's heuristic. (x, y, z) = self.set_initial_guess(self.qp, **kwargs) # Slack variables are the trailing variables in x. s = x[on:] ns = self.nSlacks # Initialize steps in dual variables. dz = np.zeros(ns) col_scale = np.empty(n) # Allocate room for right-hand side of linear systems. rhs = np.zeros(n + m) finished = False iter = 0 setup_time = cputime() # Main loop. while not finished: # Display initial header every so often. if self.verbose and iter % 20 == 0: sys.stdout.write('\n' + self.header + '\n') sys.stdout.write('-' * len(self.header) + '\n') # Compute residuals. pFeas = A * x - b comp = s * z sz = sum(comp) # comp = S z Qx = Q * x[:on] dFeas = y * A dFeas[:on] -= self.c + Qx # dFeas1 = A1'y - c - Qx dFeas[on:] += z # dFeas2 = A2'y + z # Compute duality measure. if ns > 0: mu = sz / ns else: mu = 0.0 # Compute residual norms and scaled residual norms. #pResid = norm_infty(pFeas + regdu * r)/(1+self.normc+self.normQ) #dResid = norm_infty(dFeas - regpr * q)/(1+self.normb+self.normQ) pResid = norm2(pFeas) spResid = pResid / (1 + self.normc + self.normQ) dResid = norm2(dFeas) sdResid = dResid / (1 + self.normb + self.normQ) if ns > 0: cResid = norm_infty(comp) / (self.normbc + self.normQ) else: cResid = 0.0 # Compute relative duality gap. cx = np.dot(c, x[:on]) xQx = np.dot(x[:on], Qx) by = np.dot(b, y) rgap = cx + xQx - by #rgap += regdu * (rNorm**2 + np.dot(r,y)) rgap = abs(rgap) / (1 + abs(cx) + self.normQ) rgap2 = mu / (1 + abs(cx) + self.normQ) # Compute overall residual for stopping condition. kktResid = max(spResid, sdResid, rgap2) #kktResid = max(pResid, cResid, dResid) # At the first iteration, initialize perturbation vectors # (q=primal, r=dual). # Should probably get rid of q when regpr=0 and of r when regdu=0. if iter == 0: if regpr > 0: q = dFeas / regpr qNorm = dResid / regpr rho_q = dResid else: q = dFeas qNorm = dResid rho_q = 0.0 rho_q_min = rho_q if regdu > 0: r = -pFeas / regdu rNorm = pResid / regdu del_r = pResid else: r = -pFeas rNorm = pResid del_r = 0.0 del_r_min = del_r pr_infeas_count = 0 # Used to detect primal infeasibility. du_infeas_count = 0 # Used to detect dual infeasibility. pr_last_iter = 0 du_last_iter = 0 mu0 = mu else: regdu = min(regdu / 10, sz / normdy / 10, (sz / normdy)**(1.1)) regdu = max(regdu, regdu_min) regpr = min(regpr / 10, sz / normdx / 10, (sz / normdx)**(1.1)) regpr = max(regpr, regpr_min) # Check for infeasible problem. if check_infeasible: if mu < 1.0e-8 * mu0 and rho_q > 1.0e+2 * rho_q_min: pr_infeas_count += 1 if pr_infeas_count > 1 and pr_last_iter == iter - 1: if pr_infeas_count > 6: status = 'Problem seems to be (locally) dual infeasible' short_status = 'dInf' finished = True continue pr_last_iter = iter if mu < 1.0e-8 * mu0 and del_r > 1.0e+2 * del_r_min: du_infeas_count += 1 if du_infeas_count > 1 and du_last_iter == iter - 1: if du_infeas_count > 6: status = 'Problem seems to be (locally) primal infeasible' short_status = 'pInf' finished = True continue du_last_iter = iter # Display objective and residual data. if self.verbose: sys.stdout.write(self.format1 % (iter, cx + 0.5 * xQx, pResid, dResid, cResid, rgap, qNorm, rNorm)) if kktResid <= tolerance: status = 'Optimal solution found' short_status = 'opt' finished = True continue if iter >= itermax: status = 'Maximum number of iterations reached' short_status = 'iter' finished = True continue # Record some quantities for display if ns > 0: mins = np.min(s) minz = np.min(z) maxs = np.max(s) else: mins = minz = maxs = 0 # Solve the linear system # # [ -(Q+pI) 0 A1' ] [∆x] = [c + Q x - A1' y ] # [ 0 -(S^{-1} Z + pI) A2' ] [∆s] [ - A2' y - µ S^{-1} e] # [ A1 A2 dI ] [∆y] [ b - A1 x - A2 s ] # # where s are the slack variables, p is the primal regularization # parameter, d is the dual regularization parameter, and # A = [ A1 A2 ] where the columns of A1 correspond to the original # problem variables and those of A2 correspond to slack variables. # # We recover ∆z = -z - S^{-1} (Z ∆s + µ e). # Compute augmented matrix and factorize it. factorized = False nb_bump = 0 while not factorized and nb_bump < 5: H.put(-diagQ - regpr, range(on)) H.put(-z / s - regpr, range(on, n)) H.put(regdu, range(n, n + m)) self.LBL.factorize(H) factorized = True # If the augmented matrix does not have full rank, bump up the # regularization parameters. if not self.LBL.isFullRank: if self.verbose: sys.stderr.write('Primal-Dual Matrix Rank Deficient') sys.stderr.write('... bumping up reg parameters\n') regpr *= 100 regdu *= 100 nb_bump += 1 factorized = False # Abandon if regularization is unsuccessful. if not self.LBL.isFullRank and nb_bump == 5: status = 'Unable to regularize sufficiently.' short_status = 'degn' finished = True continue if PredictorCorrector: # Use Mehrotra predictor-corrector method. # Compute affine-scaling step, i.e. with centering = 0. rhs[:n] = -dFeas rhs[on:n] += z rhs[n:] = -pFeas (step, nres, neig) = self.solveSystem(rhs) # Recover dx and dz. dx = step[:n] ds = dx[on:] dz = -z * (1 + ds / s) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, ip) = self.maxStepLength(z, dz) # Estimate duality gap after affine-scaling step. muAff = np.dot(s + alpha_p * ds, z + alpha_d * dz) / ns sigma = (muAff / mu)**3 # Incorporate predictor information for corrector step. comp += ds * dz else: # Use long-step method: Compute centering parameter. sigma = min(0.1, 100 * mu) # Assemble right-hand side with centering information. comp -= sigma * mu if PredictorCorrector: # Only update rhs[on:n]; the rest of the vector did not change. rhs[on:n] += comp / s - z else: rhs[:n] = -dFeas rhs[on:n] += comp / s rhs[n:] = -pFeas # Solve augmented system. (step, nres, neig) = self.solveSystem(rhs) # Recover step. dx = step[:n] ds = dx[on:] dy = step[n:] dz = -(comp + z * ds) / s normds = norm2(ds) normdy = norm2(dy) normdx = norm2(dx) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, id) = self.maxStepLength(z, dz) # Compute fraction-to-the-boundary factor. tau = max(.9995, 1.0 - mu) if PredictorCorrector: # Compute actual stepsize using Mehrotra's heuristic mult = 0.1 # ip=-1 if ds ≥ 0, and id=-1 if dz ≥ 0 if (ip != -1 or id != -1) and ip != id: mu_tmp = np.dot(s + alpha_p * ds, z + alpha_d * dz) / ns if ip != -1 and ip != id: zip = z[ip] + alpha_d * dz[ip] gamma_p = (mult * mu_tmp - s[ip] * zip) / (alpha_p * ds[ip] * zip) alpha_p *= max(1 - mult, gamma_p) if id != -1 and ip != id: sid = s[id] + alpha_p * ds[id] gamma_d = (mult * mu_tmp - z[id] * sid) / (alpha_d * dz[id] * sid) alpha_d *= max(1 - mult, gamma_d) if ip == id and ip != -1: # There is a division by zero in Mehrotra's heuristic # Fall back on classical rule. alpha_p *= tau alpha_d *= tau else: alpha_p *= tau alpha_d *= tau # Display data. if self.verbose: sys.stdout.write(self.format2 % (mu, alpha_p, alpha_d, nres, regpr, regdu, rho_q, del_r, mins, minz, maxs)) # Update iterates and perturbation vectors. x += alpha_p * dx # This also updates slack variables. y += alpha_d * dy z += alpha_d * dz q *= (1 - alpha_p) q += alpha_p * dx r *= (1 - alpha_d) r += alpha_d * dy qNorm = norm2(q) rNorm = norm2(r) rho_q = regpr * qNorm / (1 + self.normc) rho_q_min = min(rho_q_min, rho_q) del_r = regdu * rNorm / (1 + self.normb) del_r_min = min(del_r_min, del_r) iter += 1 solve_time = cputime() - setup_time if self.verbose: sys.stdout.write('\n') sys.stdout.write('-' * len(self.header) + '\n') # Transfer final values to class members. self.x = x self.y = y self.z = z self.iter = iter self.pResid = pResid self.cResid = cResid self.dResid = dResid self.rgap = rgap self.kktResid = kktResid self.solve_time = solve_time self.status = status self.short_status = short_status # Unscale problem if applicable. if self.prob_scaled: self.unscale() # Recompute final objective value. self.obj_value = self.c0 + cx + 0.5 * xQx return
def __init__(self, qp, **kwargs): """ Solve a convex quadratic program of the form:: minimize c' x + 1/2 x' Q x subject to A1 x + A2 s = b, (QP) s >= 0, where Q is a symmetric positive semi-definite matrix, the variables x are the original problem variables and s are slack variables. Any quadratic program may be converted to the above form by instantiation of the `SlackFramework` class. The conversion to the slack formulation is mandatory in this implementation. The method is a variant of Mehrotra's predictor-corrector method where steps are computed by solving the primal-dual system in augmented form. Primal and dual regularization parameters may be specified by the user via the opional keyword arguments `regpr` and `regdu`. Both should be positive real numbers and should not be "too large". By default they are set to 1.0 and updated at each iteration. If `scale` is set to `True`, (QP) is scaled automatically prior to solution so as to equilibrate the rows and columns of the constraint matrix [A1 A2]. Advantages of this method are that it is not sensitive to dense columns in A, no special treatment of the unbounded variables x is required, and a sparse symmetric quasi-definite system of equations is solved at each iteration. The latter, although indefinite, possesses a Cholesky-like factorization. Those properties makes the method typically more robust that a standard predictor-corrector implementation and the linear system solves are often much faster than in a traditional interior-point method in augmented form. :keywords: :scale: Perform row and column equilibration of the constraint matrix [A1 A2] prior to solution (default: `True`). :regpr: Initial value of primal regularization parameter (default: `1.0`). :regdu: Initial value of dual regularization parameter (default: `1.0`). :verbose: Turn on verbose mode (default `False`). """ if not isinstance(qp, SlackFramework): msg = 'Input problem must be an instance of SlackFramework' raise ValueError, msg self.verbose = kwargs.get('verbose', True) scale = kwargs.get('scale', True) self.qp = qp self.A = qp.A() # Constraint matrix if not isinstance(self.A, PysparseMatrix): self.A = PysparseMatrix(matrix=self.A) m, n = self.A.shape on = qp.original_n # Record number of slack variables in QP self.nSlacks = qp.n - on # Collect basic info about the problem. zero = np.zeros(n) self.b = -qp.cons(zero) # Right-hand side self.c0 = qp.obj(zero) # Constant term in objective self.c = qp.grad(zero[:on]) # Cost vector self.Q = PysparseMatrix( matrix=qp.hess(zero[:on], np.zeros(qp.original_m))) # Apply in-place problem scaling if requested. self.prob_scaled = False if scale: self.t_scale = cputime() self.scale() self.t_scale = cputime() - self.t_scale else: # self.scale() sets self.normQ to the Frobenius norm of Q # as a by-product. If we're not scaling, set normQ manually. self.normQ = self.Q.matrix.norm('fro') self.normb = norm_infty(self.b) self.normc = norm_infty(self.c) self.normbc = 1 + max(self.normb, self.normc) # Initialize augmented matrix self.H = PysparseMatrix(size=n + m, sizeHint=n + m + self.A.nnz + self.Q.nnz, symmetric=True) # The (1,1) block will always be Q (save for its diagonal). self.H[:on, :on] = -self.Q # The (2,1) block will always be A. We store it now once and for all. self.H[n:, :n] = self.A # It will be more efficient to keep the diagonal of Q around. self.diagQ = self.Q.take(range(qp.original_n)) # We perform the analyze phase on the augmented system only once. # self.LBL will be initialized in solve(). self.LBL = None # Set regularization parameters. self.regpr = kwargs.get('regpr', 1.0) self.regpr_min = 1.0e-8 self.regdu = kwargs.get('regdu', 1.0) self.regdu_min = 1.0e-8 # Check input parameters. if self.regpr < 0.0: self.regpr = 0.0 if self.regdu < 0.0: self.regdu = 0.0 # Initialize format strings for display fmt_hdr = '%-4s %9s' + ' %-8s' * 6 + ' %-7s %-4s %-4s' + ' %-8s' * 8 self.header = fmt_hdr % ('Iter', 'Cost', 'pResid', 'dResid', 'cResid', 'rGap', 'qNorm', 'rNorm', 'Mu', 'AlPr', 'AlDu', 'LS Resid', 'RegPr', 'RegDu', 'Rho q', 'Del r', 'Min(s)', 'Min(z)', 'Max(s)') self.format1 = '%-4d %9.2e' self.format1 += ' %-8.2e' * 6 self.format2 = ' %-7.1e %-4.2f %-4.2f' self.format2 += ' %-8.2e' * 8 + '\n' if self.verbose: self.display_stats() return
def solve(self, **kwargs): """ Solve the input problem with the primal-dual-regularized interior-point method. Accepted input keyword arguments are :keywords: :itermax: The maximum allowed number of iterations (default: 10n) :tolerance: Stopping tolerance (default: 1.0e-6) :PredictorCorrector: Use the predictor-corrector method (default: `True`). If set to `False`, a variant of the long-step method is used. The long-step method is generally slower and less robust. Upon exit, the following members of the class instance are set: * x..............final iterate * y..............final value of the Lagrange multipliers associated to A1 x + A2 s = b * z..............final value of the Lagrange multipliers associated to s>=0 * obj_value......final cost * iter...........total number of iterations * kktResid.......final relative residual * solve_time.....time to solve the QP * status.........string describing the exit status. * short_status...short version of status, used for printing. """ qp = self.qp itermax = kwargs.get('itermax', max(100,10*qp.n)) tolerance = kwargs.get('tolerance', 1.0e-6) PredictorCorrector = kwargs.get('PredictorCorrector', True) check_infeasible = kwargs.get('check_infeasible', True) # Transfer pointers for convenience. m, n = self.A.shape ; on = qp.original_n A = self.A ; b = self.b ; c = self.c ; Q = self.Q ; diagQ = self.diagQ H = self.H regpr = self.regpr ; regdu = self.regdu regpr_min = self.regpr_min ; regdu_min = self.regdu_min # Obtain initial point from Mehrotra's heuristic. (x,y,z) = self.set_initial_guess(self.qp, **kwargs) # Slack variables are the trailing variables in x. s = x[on:] ; ns = self.nSlacks # Initialize steps in dual variables. dz = np.zeros(ns) col_scale = np.empty(n) # Allocate room for right-hand side of linear systems. rhs = np.zeros(n+m) finished = False iter = 0 setup_time = cputime() # Main loop. while not finished: # Display initial header every so often. if self.verbose and iter % 20 == 0: sys.stdout.write('\n' + self.header + '\n') sys.stdout.write('-' * len(self.header) + '\n') # Compute residuals. pFeas = A*x - b comp = s*z ; sz = sum(comp) # comp = S z Qx = Q*x[:on] dFeas = y*A ; dFeas[:on] -= self.c + Qx # dFeas1 = A1'y - c - Qx dFeas[on:] += z # dFeas2 = A2'y + z # Compute duality measure. if ns > 0: mu = sz/ns else: mu = 0.0 # Compute residual norms and scaled residual norms. #pResid = norm_infty(pFeas + regdu * r)/(1+self.normc+self.normQ) #dResid = norm_infty(dFeas - regpr * q)/(1+self.normb+self.normQ) pResid = norm2(pFeas) ; spResid = pResid/(1+self.normc+self.normQ) dResid = norm2(dFeas) ; sdResid = dResid/(1+self.normb+self.normQ) if ns > 0: cResid = norm_infty(comp)/(self.normbc+self.normQ) else: cResid = 0.0 # Compute relative duality gap. cx = np.dot(c,x[:on]) xQx = np.dot(x[:on],Qx) by = np.dot(b,y) rgap = cx + xQx - by #rgap += regdu * (rNorm**2 + np.dot(r,y)) rgap = abs(rgap) / (1 + abs(cx) + self.normQ) rgap2 = mu / (1 + abs(cx) + self.normQ) # Compute overall residual for stopping condition. kktResid = max(spResid, sdResid, rgap2) #kktResid = max(pResid, cResid, dResid) # At the first iteration, initialize perturbation vectors # (q=primal, r=dual). # Should probably get rid of q when regpr=0 and of r when regdu=0. if iter == 0: if regpr > 0: q = dFeas/regpr ; qNorm = dResid/regpr ; rho_q = dResid else: q = dFeas ; qNorm = dResid ; rho_q = 0.0 rho_q_min = rho_q if regdu > 0: r = -pFeas/regdu ; rNorm = pResid/regdu ; del_r = pResid else: r = -pFeas ; rNorm = pResid ; del_r = 0.0 del_r_min = del_r pr_infeas_count = 0 # Used to detect primal infeasibility. du_infeas_count = 0 # Used to detect dual infeasibility. pr_last_iter = 0 du_last_iter = 0 mu0 = mu else: regdu = min(regdu/10, sz/normdy/10, (sz/normdy)**(1.1)) regdu = max(regdu, regdu_min) regpr = min(regpr/10, sz/normdx/10, (sz/normdx)**(1.1)) regpr = max(regpr, regpr_min) # Check for infeasible problem. if check_infeasible: if mu < 1.0e-8 * mu0 and rho_q > 1.0e+2 * rho_q_min: pr_infeas_count += 1 if pr_infeas_count > 1 and pr_last_iter == iter-1: if pr_infeas_count > 6: status = 'Problem seems to be (locally) dual infeasible' short_status = 'dInf' finished = True continue pr_last_iter = iter if mu < 1.0e-8 * mu0 and del_r > 1.0e+2 * del_r_min: du_infeas_count += 1 if du_infeas_count > 1 and du_last_iter == iter-1: if du_infeas_count > 6: status='Problem seems to be (locally) primal infeasible' short_status = 'pInf' finished = True continue du_last_iter = iter # Display objective and residual data. if self.verbose: sys.stdout.write(self.format1 % (iter, cx + 0.5 * xQx, pResid, dResid, cResid, rgap, qNorm, rNorm)) if kktResid <= tolerance: status = 'Optimal solution found' short_status = 'opt' finished = True continue if iter >= itermax: status = 'Maximum number of iterations reached' short_status = 'iter' finished = True continue # Record some quantities for display if ns > 0: mins = np.min(s) minz = np.min(z) maxs = np.max(s) else: mins = minz = maxs = 0 # Solve the linear system # # [ -(Q+pI) 0 A1' ] [∆x] = [c + Q x - A1' y ] # [ 0 -(S^{-1} Z + pI) A2' ] [∆s] [ - A2' y - µ S^{-1} e] # [ A1 A2 dI ] [∆y] [ b - A1 x - A2 s ] # # where s are the slack variables, p is the primal regularization # parameter, d is the dual regularization parameter, and # A = [ A1 A2 ] where the columns of A1 correspond to the original # problem variables and those of A2 correspond to slack variables. # # We recover ∆z = -z - S^{-1} (Z ∆s + µ e). # Compute augmented matrix and factorize it. factorized = False nb_bump = 0 while not factorized and nb_bump < 5: H.put(-diagQ - regpr, range(on)) H.put(-z/s - regpr, range(on,n)) H.put(regdu, range(n,n+m)) self.LBL.factorize(H) factorized = True # If the augmented matrix does not have full rank, bump up the # regularization parameters. if not self.LBL.isFullRank: if self.verbose: sys.stderr.write('Primal-Dual Matrix Rank Deficient') sys.stderr.write('... bumping up reg parameters\n') regpr *= 100 ; regdu *= 100 nb_bump += 1 factorized = False # Abandon if regularization is unsuccessful. if not self.LBL.isFullRank and nb_bump == 5: status = 'Unable to regularize sufficiently.' short_status = 'degn' finished = True continue if PredictorCorrector: # Use Mehrotra predictor-corrector method. # Compute affine-scaling step, i.e. with centering = 0. rhs[:n] = -dFeas rhs[on:n] += z rhs[n:] = -pFeas (step, nres, neig) = self.solveSystem(rhs) # Recover dx and dz. dx = step[:n] ds = dx[on:] dz = -z * (1 + ds/s) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, ip) = self.maxStepLength(z, dz) # Estimate duality gap after affine-scaling step. muAff = np.dot(s + alpha_p * ds, z + alpha_d * dz)/ns sigma = (muAff/mu)**3 # Incorporate predictor information for corrector step. comp += ds*dz else: # Use long-step method: Compute centering parameter. sigma = min(0.1, 100*mu) # Assemble right-hand side with centering information. comp -= sigma * mu if PredictorCorrector: # Only update rhs[on:n]; the rest of the vector did not change. rhs[on:n] += comp/s - z else: rhs[:n] = -dFeas rhs[on:n] += comp/s rhs[n:] = -pFeas # Solve augmented system. (step, nres, neig) = self.solveSystem(rhs) # Recover step. dx = step[:n] ds = dx[on:] dy = step[n:] dz = -(comp + z*ds)/s normds = norm2(ds) ; normdy = norm2(dy) ; normdx = norm2(dx) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, id) = self.maxStepLength(z, dz) # Compute fraction-to-the-boundary factor. tau = max(.9995, 1.0-mu) if PredictorCorrector: # Compute actual stepsize using Mehrotra's heuristic mult = 0.1 # ip=-1 if ds ≥ 0, and id=-1 if dz ≥ 0 if (ip != -1 or id != -1) and ip != id: mu_tmp = np.dot(s + alpha_p * ds, z + alpha_d * dz)/ns if ip != -1 and ip != id: zip = z[ip] + alpha_d * dz[ip] gamma_p = (mult*mu_tmp - s[ip]*zip)/(alpha_p*ds[ip]*zip) alpha_p *= max(1-mult, gamma_p) if id != -1 and ip != id: sid = s[id] + alpha_p * ds[id] gamma_d = (mult*mu_tmp - z[id]*sid)/(alpha_d*dz[id]*sid) alpha_d *= max(1-mult, gamma_d) if ip==id and ip != -1: # There is a division by zero in Mehrotra's heuristic # Fall back on classical rule. alpha_p *= tau alpha_d *= tau else: alpha_p *= tau alpha_d *= tau # Display data. if self.verbose: sys.stdout.write(self.format2 % (mu, alpha_p, alpha_d, nres, regpr, regdu, rho_q, del_r, mins, minz, maxs)) # Update iterates and perturbation vectors. x += alpha_p * dx # This also updates slack variables. y += alpha_d * dy z += alpha_d * dz q *= (1-alpha_p) ; q += alpha_p * dx r *= (1-alpha_d) ; r += alpha_d * dy qNorm = norm2(q) ; rNorm = norm2(r) rho_q = regpr * qNorm/(1+self.normc) ; rho_q_min = min(rho_q_min, rho_q) del_r = regdu * rNorm/(1+self.normb) ; del_r_min = min(del_r_min, del_r) iter += 1 solve_time = cputime() - setup_time if self.verbose: sys.stdout.write('\n') sys.stdout.write('-' * len(self.header) + '\n') # Transfer final values to class members. self.x = x self.y = y self.z = z self.iter = iter self.pResid = pResid ; self.cResid = cResid ; self.dResid = dResid self.rgap = rgap self.kktResid = kktResid self.solve_time = solve_time self.status = status self.short_status = short_status # Unscale problem if applicable. if self.prob_scaled: self.unscale() # Recompute final objective value. self.obj_value = self.c0 + cx + 0.5 * xQx return
def __init__(self, qp, **kwargs): """ Solve a convex quadratic program of the form:: minimize c' x + 1/2 x' Q x subject to A1 x + A2 s = b, (QP) s >= 0, where Q is a symmetric positive semi-definite matrix, the variables x are the original problem variables and s are slack variables. Any quadratic program may be converted to the above form by instantiation of the `SlackFramework` class. The conversion to the slack formulation is mandatory in this implementation. The method is a variant of Mehrotra's predictor-corrector method where steps are computed by solving the primal-dual system in augmented form. Primal and dual regularization parameters may be specified by the user via the opional keyword arguments `regpr` and `regdu`. Both should be positive real numbers and should not be "too large". By default they are set to 1.0 and updated at each iteration. If `scale` is set to `True`, (QP) is scaled automatically prior to solution so as to equilibrate the rows and columns of the constraint matrix [A1 A2]. Advantages of this method are that it is not sensitive to dense columns in A, no special treatment of the unbounded variables x is required, and a sparse symmetric quasi-definite system of equations is solved at each iteration. The latter, although indefinite, possesses a Cholesky-like factorization. Those properties makes the method typically more robust that a standard predictor-corrector implementation and the linear system solves are often much faster than in a traditional interior-point method in augmented form. :keywords: :scale: Perform row and column equilibration of the constraint matrix [A1 A2] prior to solution (default: `True`). :regpr: Initial value of primal regularization parameter (default: `1.0`). :regdu: Initial value of dual regularization parameter (default: `1.0`). :bump_max: Max number of times regularization parameters are increased when a factorization fails (default 5). :logger_name: Name of a logger to control output. :verbose: Turn on verbose mode (default `False`). """ if not isinstance(qp, SlackFramework): msg = 'Input problem must be an instance of SlackFramework' raise ValueError, msg # Grab logger if one was configured. logger_name = kwargs.get('logger_name', 'cqp.solver') self.log = logging.getLogger(logger_name) self.verbose = kwargs.get('verbose', True) scale = kwargs.get('scale', True) self.qp = qp self.A = qp.A() # Constraint matrix if not isinstance(self.A, PysparseMatrix): self.A = PysparseMatrix(matrix=self.A) m, n = self.A.shape ; on = qp.original_n # Record number of slack variables in QP self.nSlacks = qp.n - on # Collect basic info about the problem. zero = np.zeros(n) self.b = -qp.cons(zero) # Right-hand side self.c0 = qp.obj(zero) # Constant term in objective self.c = qp.grad(zero[:on]) # Cost vector self.Q = PysparseMatrix(matrix=qp.hess(zero[:on], np.zeros(qp.original_m))) # Apply in-place problem scaling if requested. self.prob_scaled = False if scale: self.t_scale = cputime() self.scale() self.t_scale = cputime() - self.t_scale else: # self.scale() sets self.normQ to the Frobenius norm of Q # and self.normA to the Frobenius norm of A as a by-product. # If we're not scaling, set normQ and normA manually. self.normQ = self.Q.matrix.norm('fro') self.normA = self.A.matrix.norm('fro') self.normb = norm_infty(self.b) self.normc = norm_infty(self.c) self.normbc = 1 + max(self.normb, self.normc) # Initialize augmented matrix. self.H = self.initialize_kkt_matrix() # It will be more efficient to keep the diagonal of Q around. self.diagQ = self.Q.take(range(qp.original_n)) # We perform the analyze phase on the augmented system only once. # self.LBL will be initialized in solve(). self.LBL = None # Set regularization parameters. self.regpr = kwargs.get('regpr', 1.0) ; self.regpr_min = 1.0e-8 self.regdu = kwargs.get('regdu', 1.0) ; self.regdu_min = 1.0e-8 # Max number of times regularization parameters are increased. self.bump_max = kwargs.get('bump_max', 5) # Check input parameters. if self.regpr < 0.0: self.regpr = 0.0 if self.regdu < 0.0: self.regdu = 0.0 # Initialize format strings for display fmt_hdr = '%-4s %9s' + ' %-8s'*6 + ' %-7s %-4s %-4s' + ' %-8s'*8 self.header = fmt_hdr % ('Iter', 'Cost', 'pResid', 'dResid', 'cResid', 'rGap', 'qNorm', 'rNorm', 'Mu', 'AlPr', 'AlDu', 'LS Resid', 'RegPr', 'RegDu', 'Rho q', 'Del r', 'Min(s)', 'Min(z)', 'Max(s)') self.format1 = '%-4d %9.2e' self.format1 += ' %-8.2e' * 6 self.format2 = ' %-7.1e %-4.2f %-4.2f' self.format2 += ' %-8.2e' * 8 self.cond_history = [] self.berr_history = [] self.derr_history = [] self.nrms_history = [] self.lres_history = [] if self.verbose: self.display_stats() return
M = spmatrix.ll_mat_from_mtx( sys.argv[1] ) (m,n) = M.shape if m != n: sys.stderr( 'Matrix must be square' ) sys.exit(1) if not M.issym: sys.stderr( 'Matrix must be symmetric' ) sys.exit(2) e = numpy.ones( n, 'd' ) rhs = numpy.zeros( n, 'd' ) M.matvec( e, rhs ) sys.stderr.write( ' Factorizing matrix... ' ) G = PyMa27Context( M ) sys.stderr.write( ' done\n' ) sys.stderr.write( ' Solving system... ' ) G.solve( rhs ) sys.stderr.write( ' done\n' ) sys.stderr.write( ' Residual = %-g\n' % norms.norm_infty( G.residual ) ) sys.stderr.write( ' Relative error = %-g\n' % norms.norm_infty( G.x - e ) ) sys.stderr.write( ' Performing iterative refinement if necessary... ' ) nr1 = nr = norms.norm_infty( G.residual ) nitref = 0 while nr1 > 1.0e-6 and nitref < 5 and nr1 <= nr: nitref += 1 G.refine( rhs ) nr1 = norms.norm_infty( G.residual ) sys.stderr.write( ' done\n' ) sys.stderr.write( ' After %-d refinements:\n' % nitref ) sys.stderr.write( ' Residual = %-g\n' % norms.norm_infty( G.residual ) ) sys.stderr.write( ' Relative error = %-g\n' % norms.norm_infty( G.x - e ) )
20.0 * (x[1] - x[0]**2) ], 'd') ALS = ArmijoLineSearch(tfactor = 0.2) x = array([-0.5, 1.0], 'd') xmin = xmax = x[0] ymin = ymax = x[1] f = rosenbrock(x) grad = rosengrad(x) d = -grad slope = dot(grad, d) t = 0.0 tlist = [] xlist = [x[0]] ylist = [x[1]] iter = 0 print '%-d\t%-g\t%-g\t%-g\t%-g\t%-g\t%-g' % (iter, f, norm_infty(grad), x[0], x[1], t, slope) while norm_infty(grad) > 1.0e-5: iter += 1 # Perform linesearch t = ALS.search(rosenbrock, x, d, slope, f = f) tlist.append(t) # Move ahead x += t * d xlist.append(x[0]) ylist.append(x[1]) xmin = min(xmin, x[0]) xmax = max(xmax, x[0]) ymin = min(ymin, x[1])
], 'd') ALS = ArmijoLineSearch(tfactor=0.2) x = array([-0.5, 1.0], 'd') xmin = xmax = x[0] ymin = ymax = x[1] f = rosenbrock(x) grad = rosengrad(x) d = -grad slope = dot(grad, d) t = 0.0 tlist = [] xlist = [x[0]] ylist = [x[1]] iter = 0 print '%-d\t%-g\t%-g\t%-g\t%-g\t%-g\t%-g' % (iter, f, norm_infty(grad), x[0], x[1], t, slope) while norm_infty(grad) > 1.0e-5: iter += 1 # Perform linesearch t = ALS.search(rosenbrock, x, d, slope, f=f) tlist.append(t) # Move ahead x += t * d xlist.append(x[0]) ylist.append(x[1]) xmin = min(xmin, x[0]) xmax = max(xmax, x[0])
def SolveInner(self, **kwargs): """ Perform a series of inner iterations so as to minimize the primal-dual merit function with the current value of the barrier parameter to within some given tolerance. The only optional argument recognized is stopTol stopping tolerance (default: muerrfact * mu). """ merit = self.merit nx = merit.nlp.n nz = merit.nz rho = 1 # Dummy initial value for rho niter = 0 # Dummy initial value for number of inners status = '' # Dummy initial step status alpha = 0.0 # Fraction-to-the-boundary step size if self.inexact: cgtol = 1.0 else: cgtol = -1.0 inner_iter = 0 # Inner iteration counter # Obtain starting point. (x, z) = (self.x, self.z) # Obtain first-order data at starting point. if self.iter == 0: f = nlp.obj(x) gf = nlp.grad(x) else: f = self.f gf = self.gf psi = merit.obj(x, z, f=f) g = merit.grad(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: return # Reset initial trust-region radius self.TR.Delta = 0.1 * gNorm # Set inner iteration stopping tolerance stopTol = kwargs.get('stopTol', self.muerrfact * self.mu) finished = (gNorm <= stopTol) or (self.iter >= self.maxiter) while not finished: # Print out header every so many iterations if self.verbose: if self.iter % self.printFrequency == 0: sys.stdout.write(self.hline) sys.stdout.write(self.header) sys.stdout.write(self.hline) if inner_iter == 0: sys.stdout.write(('*' + self.itFormat) % self.iter) else: sys.stdout.write((' ' + self.itFormat) % self.iter) sys.stdout.write( self.format % (f, max(norm_infty(self.dRes), norm_infty(self.cRes), norm_infty(self.pRes)), gNorm, self.mu, alpha, niter, rho, self.TR.Delta, status)) # Set stopping tolerance for trust-region subproblem. if self.inexact: cgtol = max(1.0e-8, min(0.1 * cgtol, sqrt(gNorm))) if self.debug: self._debugMsg('cgtol = ' + str(cgtol)) # Update Hessian matrix with current iteration information. # self.PDHess(x,z) if self.debug: self._debugMsg('g = ' + np.str(g)) self._debugMsg('gNorm = ' + str(gNorm)) self._debugMsg('stopTol = ' + str(stopTol)) self._debugMsg('dRes = ' + np.str(self.dRes)) self._debugMsg('cRes = ' + np.str(self.cRes)) self._debugMsg('pRes = ' + np.str(self.pRes)) self._debugMsg('optimal = ' + str(self.optimal)) # Set up the preconditioner if applicable. self.SetupPrecon() # Iteratively minimize the quadratic model in the trust region # m(s) = <g, s> + 1/2 <s, Hs> # Note that m(s) does not include f(x): m(0) = 0. H = SimpleLinearOperator(nx + nz, nx + nz, lambda v: merit.hprod(v), symmetric=True) subsolver = self.TrSolver( g, H, prec=self.Precon, radius=self.TR.Delta, reltol=cgtol, #fraction = 0.5, itmax=2 * (n + nz), #debug=True, #btol=.9, #cur_iter=np.concatenate((x,z)) ) subsolver.Solve() if self.debug: self._debugMsg('x = ' + np.str(x)) self._debugMsg('z = ' + np.str(z)) self._debugMsg('step = ' + np.str(solver.step)) self._debugMsg('step norm = ' + str(solver.stepNorm)) # Record total number of CG iterations. niter = subsolver.niter self.cgiter += subsolver.niter # Compute maximal step to the boundary and next candidate. alphax, alphaz = self.ftb(x, z, solver.step) alpha = min(alphax, alphaz) dx = subsolver.step[:n] dz = subsolver.step[n:] x_trial = x + alphax * dx z_trial = z + alphaz * dz f_trial = merit.nlp.obj(x_trial) psi_trial = merit.obj(x_trial, z_trial, f=f_trial) # Compute ratio of predicted versus achieved reduction. rho = self.TR.Rho(psi, psi_trial, subsolver.m) if self.debug: self._debugMsg('m = ' + str(solver.m)) self._debugMsg('x_trial = ' + np.str(x_trial)) self._debugMsg('z_trial = ' + np.str(z_trial)) self._debugMsg('psi_trial = ' + str(psi_trial)) self._debugMsg('rho = ' + str(rho)) # Accept or reject next candidate status = 'Rej' if rho >= self.TR.eta1: self.TR.UpdateRadius(rho, solver.stepNorm) x = x_trial z = z_trial f = f_trial psi = psi_trial gf = nlp.grad(x) g = self.GradPDMerit(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: finished = True continue status = 'Acc' else: if self.ny: # Backtracking linesearch a la "Nocedal & Yuan" slope = np.dot(g, solver.step) target = psi + 1.0e-4 * alpha * slope j = 0 while (psi_trial >= target) and (j < self.nyMax): alphax /= 1.2 alphaz /= 1.2 alpha = min(alphax, alphaz) target = psi + 1.0e-4 * alpha * slope x_trial = x + alphax * dx z_trial = z + alphaz * dz f_trial = nlp.obj(x_trial) psi_trial = self.PDMerit(x_trial, z_trial, f=f_trial) j += 1 if self.opportunistic or (j < self.nyMax): x = x_trial z = z_trial #self.PrimalMultipliers(x) f = f_trial psi = psi_trial gf = nlp.grad(x) g = self.GradPDMerit(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: finished = True continue self.TR.Delta = alpha * solver.stepNorm status = 'N-Y' else: self.TR.UpdateRadius(rho, solver.stepNorm) else: self.TR.UpdateRadius(rho, solver.stepNorm) self.UpdatePrecon() self.iter += 1 inner_iter += 1 finished = (gNorm <= stopTol) or (self.iter >= self.maxiter) if self.debug: sys.stderr.write('\n') # Store final iterate (self.x, self.z) = (x, z) self.f = f self.gf = gf self.g = g self.gNorm = gNorm self.psi = psi return
20.0 * (x[1] - x[0]**2) ], 'd') ALS = ArmijoLineSearch(tfactor = 0.2) x = array([-0.5, 1.0], 'd') xmin = xmax = x[0] ymin = ymax = x[1] f = rosenbrock(x) grad = rosengrad(x) d = -grad slope = dot(grad, d) t = 0.0 tlist = [] xlist = [x[0]] ylist = [x[1]] iter = 0 print('%-d\t%-g\t%-g\t%-g\t%-g\t%-g\t%-g' % (iter, f, norm_infty(grad), x[0], x[1], t, slope)) while norm_infty(grad) > 1.0e-5: iter += 1 # Perform linesearch t = ALS.search(rosenbrock, x, d, slope, f = f) tlist.append(t) # Move ahead x += t * d xlist.append(x[0]) ylist.append(x[1]) xmin = min(xmin, x[0]) xmax = max(xmax, x[0]) ymin = min(ymin, x[1])
M = spmatrix.ll_mat_from_mtx(sys.argv[1]) (m, n) = M.shape if m != n: sys.stderr('Matrix must be square') sys.exit(1) if not M.issym: sys.stderr('Matrix must be symmetric') sys.exit(2) e = numpy.ones(n, 'd') rhs = numpy.zeros(n, 'd') M.matvec(e, rhs) sys.stderr.write(' Factorizing matrix... ') G = PyMa27Context(M) sys.stderr.write(' done\n') sys.stderr.write(' Solving system... ') G.solve(rhs) sys.stderr.write(' done\n') sys.stderr.write(' Residual = %-g\n' % norms.norm_infty(G.residual)) sys.stderr.write(' Relative error = %-g\n' % norms.norm_infty(G.x - e)) sys.stderr.write(' Performing iterative refinement if necessary... ') nr1 = nr = norms.norm_infty(G.residual) nitref = 0 while nr1 > 1.0e-6 and nitref < 5 and nr1 <= nr: nitref += 1 G.refine(rhs) nr1 = norms.norm_infty(G.residual) sys.stderr.write(' done\n') sys.stderr.write(' After %-d refinements:\n' % nitref) sys.stderr.write(' Residual = %-g\n' % norms.norm_infty(G.residual)) sys.stderr.write(' Relative error = %-g\n' % norms.norm_infty(G.x - e))
def SolveInner(self, **kwargs): """ Perform a series of inner iterations so as to minimize the primal-dual merit function with the current value of the barrier parameter to within some given tolerance. The only optional argument recognized is stopTol stopping tolerance (default: muerrfact * mu). """ merit = self.merit ; nx = merit.nlp.n ; nz = merit.nz rho = 1 # Dummy initial value for rho niter = 0 # Dummy initial value for number of inners status = '' # Dummy initial step status alpha = 0.0 # Fraction-to-the-boundary step size if self.inexact: cgtol = 1.0 else: cgtol = -1.0 inner_iter = 0 # Inner iteration counter # Obtain starting point. (x,z) = (self.x, self.z) # Obtain first-order data at starting point. if self.iter == 0: f = nlp.obj(x) ; gf = nlp.grad(x) else: f = self.f ; gf = self.gf psi = merit.obj(x, z, f=f) g = merit.grad(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: return # Reset initial trust-region radius self.TR.Delta = 0.1*gNorm # Set inner iteration stopping tolerance stopTol = kwargs.get('stopTol', self.muerrfact * self.mu) finished = (gNorm <= stopTol) or (self.iter >= self.maxiter) while not finished: # Print out header every so many iterations if self.verbose: if self.iter % self.printFrequency == 0: sys.stdout.write(self.hline) sys.stdout.write(self.header) sys.stdout.write(self.hline) if inner_iter == 0: sys.stdout.write(('*' + self.itFormat) % self.iter) else: sys.stdout.write((' ' + self.itFormat) % self.iter) sys.stdout.write(self.format % (f, max(norm_infty(self.dRes), norm_infty(self.cRes), norm_infty(self.pRes)), gNorm, self.mu, alpha, niter, rho, self.TR.Delta, status)) # Set stopping tolerance for trust-region subproblem. if self.inexact: cgtol = max(1.0e-8, min(0.1 * cgtol, sqrt(gNorm))) if self.debug: self._debugMsg('cgtol = ' + str(cgtol)) # Update Hessian matrix with current iteration information. # self.PDHess(x,z) if self.debug: self._debugMsg('g = ' + np.str(g)) self._debugMsg('gNorm = ' + str(gNorm)) self._debugMsg('stopTol = ' + str(stopTol)) self._debugMsg('dRes = ' + np.str(self.dRes)) self._debugMsg('cRes = ' + np.str(self.cRes)) self._debugMsg('pRes = ' + np.str(self.pRes)) self._debugMsg('optimal = ' + str(self.optimal)) # Set up the preconditioner if applicable. self.SetupPrecon() # Iteratively minimize the quadratic model in the trust region # m(s) = <g, s> + 1/2 <s, Hs> # Note that m(s) does not include f(x): m(0) = 0. H = SimpleLinearOperator(nx+nz, nx+nz, lambda v: merit.hprod(v), symmetric=True) subsolver = self.TrSolver(g, H, prec = self.Precon, radius = self.TR.Delta, reltol = cgtol, #fraction = 0.5, itmax = 2*(n+nz), #debug=True, #btol=.9, #cur_iter=np.concatenate((x,z)) ) subsolver.Solve() if self.debug: self._debugMsg('x = ' + np.str(x)) self._debugMsg('z = ' + np.str(z)) self._debugMsg('step = ' + np.str(solver.step)) self._debugMsg('step norm = ' + str(solver.stepNorm)) # Record total number of CG iterations. niter = subsolver.niter self.cgiter += subsolver.niter # Compute maximal step to the boundary and next candidate. alphax, alphaz = self.ftb(x, z, solver.step) alpha = min(alphax, alphaz) dx = subsolver.step[:n] ; dz = subsolver.step[n:] x_trial = x + alphax * dx z_trial = z + alphaz * dz f_trial = merit.nlp.obj(x_trial) psi_trial = merit.obj(x_trial, z_trial, f=f_trial) # Compute ratio of predicted versus achieved reduction. rho = self.TR.Rho(psi, psi_trial, subsolver.m) if self.debug: self._debugMsg('m = ' + str(solver.m)) self._debugMsg('x_trial = ' + np.str(x_trial)) self._debugMsg('z_trial = ' + np.str(z_trial)) self._debugMsg('psi_trial = ' + str(psi_trial)) self._debugMsg('rho = ' + str(rho)) # Accept or reject next candidate status = 'Rej' if rho >= self.TR.eta1: self.TR.UpdateRadius(rho, solver.stepNorm) x = x_trial z = z_trial f = f_trial psi = psi_trial gf = nlp.grad(x) g = self.GradPDMerit(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: finished = True continue status = 'Acc' else: if self.ny: # Backtracking linesearch a la "Nocedal & Yuan" slope = np.dot(g, solver.step) target = psi + 1.0e-4 * alpha * slope j = 0 while (psi_trial >= target) and (j < self.nyMax): alphax /= 1.2 ; alphaz /= 1.2 alpha = min(alphax, alphaz) target = psi + 1.0e-4 * alpha * slope x_trial = x + alphax * dx z_trial = z + alphaz * dz f_trial = nlp.obj(x_trial) psi_trial = self.PDMerit(x_trial, z_trial, f=f_trial) j += 1 if self.opportunistic or (j < self.nyMax): x = x_trial z = z_trial #self.PrimalMultipliers(x) f = f_trial psi = psi_trial gf = nlp.grad(x) g = self.GradPDMerit(x, z, g=gf, check_optimal=True) gNorm = norm2(g) if self.optimal: finished = True continue self.TR.Delta = alpha * solver.stepNorm status = 'N-Y' else: self.TR.UpdateRadius(rho, solver.stepNorm) else: self.TR.UpdateRadius(rho, solver.stepNorm) self.UpdatePrecon() self.iter += 1 inner_iter += 1 finished = (gNorm <= stopTol) or (self.iter >= self.maxiter) if self.debug: sys.stderr.write('\n') # Store final iterate (self.x, self.z) = (x, z) self.f = f self.gf = gf self.g = g self.gNorm = gNorm self.psi = psi return
def __init__(self, lp, **kwargs): """ Solve a linear program of the form:: minimize c' x subject to A1 x + A2 s = b and s >= 0, (LP) where the variables x are the original problem variables and s are slack variables. Any linear program may be converted to the above form by instantiation of the `SlackFramework` class. The conversion to the slack formulation is mandatory in this implementation. The method is a variant of Mehrotra's predictor-corrector method where steps are computed by solving the primal-dual system in augmented form. Primal and dual regularization parameters may be specified by the user via the opional keyword arguments `regpr` and `regdu`. Both should be positive real numbers and should not be "too large". By default they are set to 1.0 and updated at each iteration. If `scale` is set to `True`, (LP) is scaled automatically prior to solution so as to equilibrate the rows and columns of the constraint matrix [A1 A2]. Advantages of this method are that it is not sensitive to dense columns in A, no special treatment of the unbounded variables x is required, and a sparse symmetric quasi-definite system of equations is solved at each iteration. The latter, although indefinite, possesses a Cholesky-like factorization. Those properties makes the method typically more robust that a standard predictor-corrector implementation and the linear system solves are often much faster than in a traditional interior-point method in augmented form. :keywords: :scale: Perform row and column equilibration of the constraint matrix [A1 A2] prior to solution (default: `True`). :stabilize: Scale the linear system to be solved at each iteration (default: `True`). :regpr: Initial value of primal regularization parameter (default: `1.0`). :regdu: Initial value of dual regularization parameter (default: `1.0`). :verbose: Turn on verbose mode (default `False`). """ if not isinstance(lp, SlackFramework): msg = 'Input problem must be an instance of SlackFramework' raise ValueError, msg scale = kwargs.get('scale', True) self.verbose = kwargs.get('verbose', True) self.stabilize = kwargs.get('stabilize', True) self.lp = lp self.A = lp.A() # Constraint matrix if not isinstance(self.A, PysparseMatrix): self.A = PysparseMatrix(matrix=self.A) m, n = self.A.shape # Record number of slack variables in LP self.nSlacks = lp.n - lp.original_n # Constant vectors zero = np.zeros(n) self.b = -lp.cons(zero) # Right-hand side self.c0 = lp.obj(zero) # Constant term in objective self.c = lp.grad(zero[:lp.original_n]) #lp.cost() # Cost vector # Apply in-place problem scaling if requested. self.prob_scaled = False if scale: self.t_scale = cputime() self.scale() self.t_scale = cputime() - self.t_scale else: # scale() sets self.normA to the Frobenius norm of A as a # by-product. Set it manually here if scaling is not enabled. self.normA = self.A.matrix.norm('fro') self.normb = norm_infty(self.b) #norm2(self.b) self.normc = norm_infty(self.c) #norm2(self.c) self.normbc = 1 + max(self.normb, self.normc) # Initialize augmented matrix self.H = PysparseMatrix(size=n+m, sizeHint=n+m+self.A.nnz, symmetric=True) # We perform the analyze phase on the augmented system only once. # self.LBL will be initialized in set_initial_guess(). self.LBL = None self.regpr = kwargs.get('regpr', 1.0) ; self.regpr_min = 1.0e-8 self.regdu = kwargs.get('regdu', 1.0) ; self.regdu_min = 1.0e-8 # Check input parameters. if self.regpr < 0.0: self.regpr = 0.0 if self.regdu < 0.0: self.regdu = 0.0 # Dual regularization is necessary for stabilization. if self.regdu == 0.0: sys.stderr.write('Warning: No dual regularization in effect\n') sys.stderr.write(' Stabilization has been turned off\n') self.stabilize = False # Initialize format strings for display fmt_hdr = '%-4s %9s' + ' %-8s'*6 + ' %-7s %-4s %-4s' + ' %-8s'*8 self.header = fmt_hdr % ('Iter', 'Cost', 'pResid', 'dResid', 'cResid', 'rGap', 'qNorm', 'rNorm', 'Mu', 'AlPr', 'AlDu', 'LS Resid', 'RegPr', 'RegDu', 'Rho q', 'Del r', 'Min(s)', 'Min(z)', 'Max(s)') self.format1 = '%-4d %9.2e' self.format1 += ' %-8.2e' * 6 self.format2 = ' %-7.1e %-4.2f %-4.2f' self.format2 += ' %-8.2e' * 8 + '\n' if self.verbose: self.display_stats() return
def solve(self, **kwargs): """ Solve the input problem with the primal-dual-regularized interior-point method. Accepted input keyword arguments are :keywords: :itermax: The maximum allowed number of iterations (default: 10n) :tolerance: Stopping tolerance (default: 1.0e-6) :PredictorCorrector: Use the predictor-corrector method (default: `True`). If set to `False`, a variant of the long-step method is used. The long-step method is generally slower and less robust. :returns: :x: final iterate :y: final value of the Lagrange multipliers associated to `A1 x + A2 s = b` :z: final value of the Lagrange multipliers associated to `s >= 0` :obj_value: final cost :iter: total number of iterations :kktResid: final relative residual :solve_time: time to solve the QP :status: string describing the exit status. :short_status: short version of status, used for printing. """ qp = self.qp itermax = kwargs.get('itermax', max(100, 10 * qp.n)) tolerance = kwargs.get('tolerance', 1.0e-6) PredictorCorrector = kwargs.get('PredictorCorrector', True) check_infeasible = kwargs.get('check_infeasible', True) # Transfer pointers for convenience. m, n = self.A.shape on = qp.original_n A = self.A b = self.b c = self.c Q = self.Q diagQ = self.diagQ H = self.H regpr = self.regpr regdu = self.regdu regpr_min = self.regpr_min regdu_min = self.regdu_min # Obtain initial point from Mehrotra's heuristic. (x, y, z) = self.set_initial_guess(**kwargs) # Slack variables are the trailing variables in x. s = x[on:] ns = self.nSlacks # Initialize steps in dual variables. dz = np.zeros(ns) # Allocate room for right-hand side of linear systems. rhs = self.initialize_rhs() finished = False iter = 0 setup_time = cputime() # Main loop. while not finished: # Display initial header every so often. if iter % 50 == 0: self.log.info(self.header) self.log.info('-' * len(self.header)) # Compute residuals. pFeas = A * x - b comp = s * z sz = sum(comp) # comp = Sz Qx = Q * x[:on] dFeas = y * A dFeas[:on] -= self.c + Qx # dFeas1 = A1'y - c - Qx dFeas[on:] += z # dFeas2 = A2'y + z # Compute duality measure. if ns > 0: mu = sz / ns else: mu = 0.0 # Compute residual norms and scaled residual norms. pResid = norm2(pFeas) spResid = pResid / (1 + self.normb + self.normA + self.normQ) dResid = norm2(dFeas) sdResid = dResid / (1 + self.normc + self.normA + self.normQ) if ns > 0: cResid = norm_infty(comp) / (self.normbc + self.normA + self.normQ) else: cResid = 0.0 # Compute relative duality gap. cx = np.dot(c, x[:on]) xQx = np.dot(x[:on], Qx) by = np.dot(b, y) rgap = cx + xQx - by rgap = abs(rgap) / (1 + abs(cx) + self.normA + self.normQ) rgap2 = mu / (1 + abs(cx) + self.normA + self.normQ) # Compute overall residual for stopping condition. kktResid = max(spResid, sdResid, rgap2) # At the first iteration, initialize perturbation vectors # (q=primal, r=dual). # Should probably get rid of q when regpr=0 and of r when regdu=0. if iter == 0: if regpr > 0: q = dFeas / regpr qNorm = dResid / regpr rho_q = dResid else: q = dFeas qNorm = dResid rho_q = 0.0 rho_q_min = rho_q if regdu > 0: r = -pFeas / regdu rNorm = pResid / regdu del_r = pResid else: r = -pFeas rNorm = pResid del_r = 0.0 del_r_min = del_r pr_infeas_count = 0 # Used to detect primal infeasibility. du_infeas_count = 0 # Used to detect dual infeasibility. pr_last_iter = 0 du_last_iter = 0 mu0 = mu else: if regdu > 0: regdu = regdu / 10 regdu = max(regdu, regdu_min) if regpr > 0: regpr = regpr / 10 regpr = max(regpr, regpr_min) # Check for infeasible problem. if check_infeasible: if mu < tolerance/100 * mu0 and \ rho_q > 1./tolerance/1.0e+6 * rho_q_min: pr_infeas_count += 1 if pr_infeas_count > 1 and pr_last_iter == iter - 1: if pr_infeas_count > 6: status = 'Problem seems to be (locally) dual' status += ' infeasible' short_status = 'dInf' finished = True continue pr_last_iter = iter else: pr_infeas_count = 0 if mu < tolerance/100 * mu0 and \ del_r > 1./tolerance/1.0e+6 * del_r_min: du_infeas_count += 1 if du_infeas_count > 1 and du_last_iter == iter - 1: if du_infeas_count > 6: status = 'Problem seems to be (locally) primal' status += ' infeasible' short_status = 'pInf' finished = True continue du_last_iter = iter else: du_infeas_count = 0 # Display objective and residual data. output_line = self.format1 % (iter, cx + 0.5 * xQx, pResid, dResid, cResid, rgap, qNorm, rNorm) if kktResid <= tolerance: status = 'Optimal solution found' short_status = 'opt' finished = True continue if iter >= itermax: status = 'Maximum number of iterations reached' short_status = 'iter' finished = True continue # Record some quantities for display if ns > 0: mins = np.min(s) minz = np.min(z) maxs = np.max(s) else: mins = minz = maxs = 0 # Compute augmented matrix and factorize it. factorized = False degenerate = False nb_bump = 0 while not factorized and not degenerate: self.update_linear_system(s, z, regpr, regdu) self.log.debug('Factorizing') self.LBL.factorize(H) factorized = True # If the augmented matrix does not have full rank, bump up the # regularization parameters. if not self.LBL.isFullRank: if self.verbose: self.log.info('Primal-Dual Matrix Rank Deficient' + \ '... bumping up reg parameters') if regpr == 0. and regdu == 0.: degenerate = True else: if regpr > 0: regpr *= 100 if regdu > 0: regdu *= 100 nb_bump += 1 degenerate = nb_bump > self.bump_max factorized = False # Abandon if regularization is unsuccessful. if not self.LBL.isFullRank and degenerate: status = 'Unable to regularize sufficiently.' short_status = 'degn' finished = True continue if PredictorCorrector: # Use Mehrotra predictor-corrector method. # Compute affine-scaling step, i.e. with centering = 0. self.set_affine_scaling_rhs(rhs, pFeas, dFeas, s, z) (step, nres, neig) = self.solveSystem(rhs) # Recover dx and dz. dx, ds, dy, dz = self.get_affine_scaling_dxsyz( step, x, s, y, z) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, ip) = self.maxStepLength(z, dz) # Estimate duality gap after affine-scaling step. muAff = np.dot(s + alpha_p * ds, z + alpha_d * dz) / ns sigma = (muAff / mu)**3 # Incorporate predictor information for corrector step. # Only update rhs[on:n]; the rest of the vector did not change. comp += ds * dz comp -= sigma * mu self.update_corrector_rhs(rhs, s, z, comp) else: # Use long-step method: Compute centering parameter. sigma = min(0.1, 100 * mu) comp -= sigma * mu # Assemble rhs. self.update_long_step_rhs(rhs, pFeas, dFeas, comp, s) # Solve augmented system. (step, nres, neig) = self.solveSystem(rhs) # Recover step. dx, ds, dy, dz = self.get_dxsyz(step, x, s, y, z, comp) normds = norm2(ds) normdy = norm2(dy) normdx = norm2(dx) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, id) = self.maxStepLength(z, dz) # Compute fraction-to-the-boundary factor. tau = max(.9995, 1.0 - mu) if PredictorCorrector: # Compute actual stepsize using Mehrotra's heuristic. mult = 0.1 # ip=-1 if ds ≥ 0, and id=-1 if dz ≥ 0 if (ip != -1 or id != -1) and ip != id: mu_tmp = np.dot(s + alpha_p * ds, z + alpha_d * dz) / ns if ip != -1 and ip != id: zip = z[ip] + alpha_d * dz[ip] gamma_p = (mult * mu_tmp - s[ip] * zip) / (alpha_p * ds[ip] * zip) alpha_p *= max(1 - mult, gamma_p) if id != -1 and ip != id: sid = s[id] + alpha_p * ds[id] gamma_d = (mult * mu_tmp - z[id] * sid) / (alpha_d * dz[id] * sid) alpha_d *= max(1 - mult, gamma_d) if ip == id and ip != -1: # There is a division by zero in Mehrotra's heuristic # Fall back on classical rule. alpha_p *= tau alpha_d *= tau else: alpha_p *= tau alpha_d *= tau # Display data. output_line += self.format2 % (mu, alpha_p, alpha_d, nres, regpr, regdu, rho_q, del_r, mins, minz, maxs) self.log.info(output_line) # Update iterates and perturbation vectors. x += alpha_p * dx # This also updates slack variables. y += alpha_d * dy z += alpha_d * dz q *= (1 - alpha_p) q += alpha_p * dx r *= (1 - alpha_d) r += alpha_d * dy qNorm = norm2(q) rNorm = norm2(r) if regpr > 0: rho_q = regpr * qNorm / (1 + self.normc) rho_q_min = min(rho_q_min, rho_q) else: rho_q = 0.0 if regdu > 0: del_r = regdu * rNorm / (1 + self.normb) del_r_min = min(del_r_min, del_r) else: del_r = 0.0 iter += 1 solve_time = cputime() - setup_time self.log.info('-' * len(self.header)) # Transfer final values to class members. self.x = x self.y = y self.z = z self.iter = iter self.pResid = pResid self.cResid = cResid self.dResid = dResid self.rgap = rgap self.kktResid = kktResid self.solve_time = solve_time self.status = status self.short_status = short_status # Unscale problem if applicable. if self.prob_scaled: self.unscale() # Recompute final objective value. self.obj_value = self.c0 + cx + 0.5 * xQx return
def solve(self, **kwargs): """ Solve the input problem with the primal-dual-regularized interior-point method. Accepted input keyword arguments are :keywords: :itermax: The maximum allowed number of iterations (default: 10n) :tolerance: Stopping tolerance (default: 1.0e-6) :PredictorCorrector: Use the predictor-corrector method (default: `True`). If set to `False`, a variant of the long-step method is used. The long-step method is generally slower and less robust. :returns: :x: final iterate :y: final value of the Lagrange multipliers associated to `A1 x + A2 s = b` :z: final value of the Lagrange multipliers associated to `s >= 0` :obj_value: final cost :iter: total number of iterations :kktResid: final relative residual :solve_time: time to solve the QP :status: string describing the exit status. :short_status: short version of status, used for printing. """ qp = self.qp itermax = kwargs.get('itermax', max(100,10*qp.n)) tolerance = kwargs.get('tolerance', 1.0e-6) PredictorCorrector = kwargs.get('PredictorCorrector', True) check_infeasible = kwargs.get('check_infeasible', True) # Transfer pointers for convenience. m, n = self.A.shape ; on = qp.original_n A = self.A ; b = self.b ; c = self.c ; Q = self.Q ; diagQ = self.diagQ H = self.H regpr = self.regpr ; regdu = self.regdu regpr_min = self.regpr_min ; regdu_min = self.regdu_min # Obtain initial point from Mehrotra's heuristic. (x,y,z) = self.set_initial_guess(**kwargs) # Slack variables are the trailing variables in x. s = x[on:] ; ns = self.nSlacks # Initialize steps in dual variables. dz = np.zeros(ns) # Allocate room for right-hand side of linear systems. rhs = self.initialize_rhs() finished = False iter = 0 setup_time = cputime() # Main loop. while not finished: # Display initial header every so often. if iter % 50 == 0: self.log.info(self.header) self.log.info('-' * len(self.header)) # Compute residuals. pFeas = A*x - b comp = s*z ; sz = sum(comp) # comp = Sz Qx = Q*x[:on] dFeas = y*A ; dFeas[:on] -= self.c + Qx # dFeas1 = A1'y - c - Qx dFeas[on:] += z # dFeas2 = A2'y + z # Compute duality measure. if ns > 0: mu = sz/ns else: mu = 0.0 # Compute residual norms and scaled residual norms. pResid = norm2(pFeas) spResid = pResid/(1+self.normb+self.normA+self.normQ) dResid = norm2(dFeas) sdResid = dResid/(1+self.normc+self.normA+self.normQ) if ns > 0: cResid = norm_infty(comp)/(self.normbc+self.normA+self.normQ) else: cResid = 0.0 # Compute relative duality gap. cx = np.dot(c,x[:on]) xQx = np.dot(x[:on],Qx) by = np.dot(b,y) rgap = cx + xQx - by rgap = abs(rgap) / (1 + abs(cx) + self.normA + self.normQ) rgap2 = mu / (1 + abs(cx) + self.normA + self.normQ) # Compute overall residual for stopping condition. kktResid = max(spResid, sdResid, rgap2) # At the first iteration, initialize perturbation vectors # (q=primal, r=dual). # Should probably get rid of q when regpr=0 and of r when regdu=0. if iter == 0: if regpr > 0: q = dFeas/regpr ; qNorm = dResid/regpr ; rho_q = dResid else: q = dFeas ; qNorm = dResid ; rho_q = 0.0 rho_q_min = rho_q if regdu > 0: r = -pFeas/regdu ; rNorm = pResid/regdu ; del_r = pResid else: r = -pFeas ; rNorm = pResid ; del_r = 0.0 del_r_min = del_r pr_infeas_count = 0 # Used to detect primal infeasibility. du_infeas_count = 0 # Used to detect dual infeasibility. pr_last_iter = 0 du_last_iter = 0 mu0 = mu else: if regdu > 0: regdu = regdu/10 regdu = max(regdu, regdu_min) if regpr > 0: regpr = regpr/10 regpr = max(regpr, regpr_min) # Check for infeasible problem. if check_infeasible: if mu < tolerance/100 * mu0 and \ rho_q > 1./tolerance/1.0e+6 * rho_q_min: pr_infeas_count += 1 if pr_infeas_count > 1 and pr_last_iter == iter-1: if pr_infeas_count > 6: status = 'Problem seems to be (locally) dual' status += ' infeasible' short_status = 'dInf' finished = True continue pr_last_iter = iter else: pr_infeas_count = 0 if mu < tolerance/100 * mu0 and \ del_r > 1./tolerance/1.0e+6 * del_r_min: du_infeas_count += 1 if du_infeas_count > 1 and du_last_iter == iter-1: if du_infeas_count > 6: status = 'Problem seems to be (locally) primal' status += ' infeasible' short_status = 'pInf' finished = True continue du_last_iter = iter else: du_infeas_count = 0 # Display objective and residual data. output_line = self.format1 % (iter, cx + 0.5 * xQx, pResid, dResid, cResid, rgap, qNorm, rNorm) if kktResid <= tolerance: status = 'Optimal solution found' short_status = 'opt' finished = True continue if iter >= itermax: status = 'Maximum number of iterations reached' short_status = 'iter' finished = True continue # Record some quantities for display if ns > 0: mins = np.min(s) minz = np.min(z) maxs = np.max(s) else: mins = minz = maxs = 0 # Compute augmented matrix and factorize it. factorized = False degenerate = False nb_bump = 0 while not factorized and not degenerate: self.update_linear_system(s, z, regpr, regdu) self.log.debug('Factorizing') self.LBL.factorize(H) factorized = True # If the augmented matrix does not have full rank, bump up the # regularization parameters. if not self.LBL.isFullRank: if self.verbose: self.log.info('Primal-Dual Matrix Rank Deficient' + \ '... bumping up reg parameters') if regpr == 0. and regdu == 0.: degenerate = True else: if regpr > 0: regpr *= 100 if regdu > 0: regdu *= 100 nb_bump += 1 degenerate = nb_bump > self.bump_max factorized = False # Abandon if regularization is unsuccessful. if not self.LBL.isFullRank and degenerate: status = 'Unable to regularize sufficiently.' short_status = 'degn' finished = True continue if PredictorCorrector: # Use Mehrotra predictor-corrector method. # Compute affine-scaling step, i.e. with centering = 0. self.set_affine_scaling_rhs(rhs, pFeas, dFeas, s, z) (step, nres, neig) = self.solveSystem(rhs) # Recover dx and dz. dx, ds, dy, dz = self.get_affine_scaling_dxsyz(step, x, s, y, z) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, ip) = self.maxStepLength(z, dz) # Estimate duality gap after affine-scaling step. muAff = np.dot(s + alpha_p * ds, z + alpha_d * dz)/ns sigma = (muAff/mu)**3 # Incorporate predictor information for corrector step. # Only update rhs[on:n]; the rest of the vector did not change. comp += ds*dz comp -= sigma * mu self.update_corrector_rhs(rhs, s, z, comp) else: # Use long-step method: Compute centering parameter. sigma = min(0.1, 100*mu) comp -= sigma * mu # Assemble rhs. self.update_long_step_rhs(rhs, pFeas, dFeas, comp, s) # Solve augmented system. (step, nres, neig) = self.solveSystem(rhs) # Recover step. dx, ds, dy, dz = self.get_dxsyz(step, x, s, y, z, comp) normds = norm2(ds) ; normdy = norm2(dy) ; normdx = norm2(dx) # Compute largest allowed primal and dual stepsizes. (alpha_p, ip) = self.maxStepLength(s, ds) (alpha_d, id) = self.maxStepLength(z, dz) # Compute fraction-to-the-boundary factor. tau = max(.9995, 1.0-mu) if PredictorCorrector: # Compute actual stepsize using Mehrotra's heuristic. mult = 0.1 # ip=-1 if ds ≥ 0, and id=-1 if dz ≥ 0 if (ip != -1 or id != -1) and ip != id: mu_tmp = np.dot(s + alpha_p * ds, z + alpha_d * dz)/ns if ip != -1 and ip != id: zip = z[ip] + alpha_d * dz[ip] gamma_p = (mult*mu_tmp - s[ip]*zip)/(alpha_p*ds[ip]*zip) alpha_p *= max(1-mult, gamma_p) if id != -1 and ip != id: sid = s[id] + alpha_p * ds[id] gamma_d = (mult*mu_tmp - z[id]*sid)/(alpha_d*dz[id]*sid) alpha_d *= max(1-mult, gamma_d) if ip==id and ip != -1: # There is a division by zero in Mehrotra's heuristic # Fall back on classical rule. alpha_p *= tau alpha_d *= tau else: alpha_p *= tau alpha_d *= tau # Display data. output_line += self.format2 % (mu, alpha_p, alpha_d, nres, regpr, regdu, rho_q, del_r, mins, minz, maxs) self.log.info(output_line) # Update iterates and perturbation vectors. x += alpha_p * dx # This also updates slack variables. y += alpha_d * dy z += alpha_d * dz q *= (1-alpha_p) ; q += alpha_p * dx r *= (1-alpha_d) ; r += alpha_d * dy qNorm = norm2(q) ; rNorm = norm2(r) if regpr > 0: rho_q = regpr * qNorm/(1+self.normc) rho_q_min = min(rho_q_min, rho_q) else: rho_q = 0.0 if regdu > 0: del_r = regdu * rNorm/(1+self.normb) del_r_min = min(del_r_min, del_r) else: del_r = 0.0 iter += 1 solve_time = cputime() - setup_time self.log.info('-' * len(self.header)) # Transfer final values to class members. self.x = x self.y = y self.z = z self.iter = iter self.pResid = pResid ; self.cResid = cResid ; self.dResid = dResid self.rgap = rgap self.kktResid = kktResid self.solve_time = solve_time self.status = status self.short_status = short_status # Unscale problem if applicable. if self.prob_scaled: self.unscale() # Recompute final objective value. self.obj_value = self.c0 + cx + 0.5 * xQx return
def __init__(self, lp, **kwargs): """ Solve a linear program of the form:: minimize c' x subject to A1 x + A2 s = b and s >= 0, (LP) where the variables x are the original problem variables and s are slack variables. Any linear program may be converted to the above form by instantiation of the `SlackFramework` class. The conversion to the slack formulation is mandatory in this implementation. The method is a variant of Mehrotra's predictor-corrector method where steps are computed by solving the primal-dual system in augmented form. Primal and dual regularization parameters may be specified by the user via the opional keyword arguments `regpr` and `regdu`. Both should be positive real numbers and should not be "too large". By default they are set to 1.0 and updated at each iteration. If `scale` is set to `True`, (LP) is scaled automatically prior to solution so as to equilibrate the rows and columns of the constraint matrix [A1 A2]. Advantages of this method are that it is not sensitive to dense columns in A, no special treatment of the unbounded variables x is required, and a sparse symmetric quasi-definite system of equations is solved at each iteration. The latter, although indefinite, possesses a Cholesky-like factorization. Those properties makes the method typically more robust that a standard predictor-corrector implementation and the linear system solves are often much faster than in a traditional interior-point method in augmented form. :keywords: :scale: Perform row and column equilibration of the constraint matrix [A1 A2] prior to solution (default: `True`). :stabilize: Scale the linear system to be solved at each iteration (default: `True`). :regpr: Initial value of primal regularization parameter (default: `1.0`). :regdu: Initial value of dual regularization parameter (default: `1.0`). :verbose: Turn on verbose mode (default `False`). """ if not isinstance(lp, SlackFramework): msg = 'Input problem must be an instance of SlackFramework' raise ValueError, msg scale = kwargs.get('scale', True) self.verbose = kwargs.get('verbose', True) self.stabilize = kwargs.get('stabilize', True) self.lp = lp self.A = lp.A() # Constraint matrix if not isinstance(self.A, PysparseMatrix): self.A = PysparseMatrix(matrix=self.A) m, n = self.A.shape # Record number of slack variables in LP self.nSlacks = lp.n - lp.original_n # Constant vectors zero = np.zeros(n) self.b = -lp.cons(zero) # Right-hand side self.c0 = lp.obj(zero) # Constant term in objective self.c = lp.grad(zero[:lp.original_n]) #lp.cost() # Cost vector # Apply in-place problem scaling if requested. self.prob_scaled = False if scale: self.t_scale = cputime() self.scale() self.t_scale = cputime() - self.t_scale else: # scale() sets self.normA to the Frobenius norm of A as a # by-product. Set it manually here if scaling is not enabled. self.normA = self.A.matrix.norm('fro') self.normb = norm_infty(self.b) #norm2(self.b) self.normc = norm_infty(self.c) #norm2(self.c) self.normbc = 1 + max(self.normb, self.normc) # Initialize augmented matrix self.H = PysparseMatrix(size=n + m, sizeHint=n + m + self.A.nnz, symmetric=True) # We perform the analyze phase on the augmented system only once. # self.LBL will be initialized in set_initial_guess(). self.LBL = None self.regpr = kwargs.get('regpr', 1.0) self.regpr_min = 1.0e-8 self.regdu = kwargs.get('regdu', 1.0) self.regdu_min = 1.0e-8 # Check input parameters. if self.regpr < 0.0: self.regpr = 0.0 if self.regdu < 0.0: self.regdu = 0.0 # Dual regularization is necessary for stabilization. if self.regdu == 0.0: sys.stderr.write('Warning: No dual regularization in effect\n') sys.stderr.write(' Stabilization has been turned off\n') self.stabilize = False # Initialize format strings for display fmt_hdr = '%-4s %9s' + ' %-8s' * 6 + ' %-7s %-4s %-4s' + ' %-8s' * 8 self.header = fmt_hdr % ('Iter', 'Cost', 'pResid', 'dResid', 'cResid', 'rGap', 'qNorm', 'rNorm', 'Mu', 'AlPr', 'AlDu', 'LS Resid', 'RegPr', 'RegDu', 'Rho q', 'Del r', 'Min(s)', 'Min(z)', 'Max(s)') self.format1 = '%-4d %9.2e' self.format1 += ' %-8.2e' * 6 self.format2 = ' %-7.1e %-4.2f %-4.2f' self.format2 += ' %-8.2e' * 8 + '\n' if self.verbose: self.display_stats() return