Example #1
0
 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
Example #2
0
 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
Example #3
0
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)
Example #4
0
# 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)
Example #5
0
# 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)
Example #6
0
    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
Example #7
0
    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
Example #8
0
File: cqp.py Project: b45ch1/nlpy
    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
Example #9
0
File: cqp.py Project: joeywen/nlpy
    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
Example #10
0
    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 ) )
Example #11
0
                        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])
Example #12
0
        ], '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])
Example #13
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
Example #14
0
                        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])
Example #15
0
    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))
Example #16
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
Example #17
0
File: lp.py Project: mpf/nlpy
    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
Example #18
0
    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
Example #19
0
File: cqp.py Project: joeywen/nlpy
    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
Example #20
0
    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