/************************************************************************* Optimum of A subject to: a) active boundary constraints (given by ActiveSet[] and corresponding elements of XC) b) active linear constraints (given by C, R, LagrangeC) INPUT PARAMETERS: A - main quadratic term of the model; although structure may store linear and rank-K terms, these terms are ignored and rewritten by this function. ANorm - estimate of ||A|| (2-norm is used) B - array[N], linear term of the model XN - possibly preallocated buffer Tmp - temporary buffer (automatically resized) Tmp1 - temporary buffer (automatically resized) OUTPUT PARAMETERS: A - modified quadratic model (this function changes rank-K term and linear term of the model) LagrangeC- current estimate of the Lagrange coefficients XN - solution RESULT: True on success, False on failure (non-SPD model) -- ALGLIB -- Copyright 20.06.2012 by Bochkanov Sergey *************************************************************************/ private static bool constrainedoptimum(sactivesets.sactiveset sas, cqmodels.convexquadraticmodel a, double anorm, double[] b, ref double[] xn, int n, ref double[] tmp, ref bool[] tmpb, ref double[] lagrangec) { bool result = new bool(); int itidx = 0; int i = 0; double v = 0; double feaserrold = 0; double feaserrnew = 0; double theta = 0; int i_ = 0; // // Rebuild basis accroding to current active set. // We call SASRebuildBasis() to make sure that fields of SAS // store up to date values. // sactivesets.sasrebuildbasis(sas); // // Allocate temporaries. // apserv.rvectorsetlengthatleast(ref tmp, Math.Max(n, sas.basissize)); apserv.bvectorsetlengthatleast(ref tmpb, n); apserv.rvectorsetlengthatleast(ref lagrangec, sas.basissize); // // Prepare model // for(i=0; i<=sas.basissize-1; i++) { tmp[i] = sas.pbasis[i,n]; } theta = 100.0*anorm; for(i=0; i<=n-1; i++) { if( sas.activeset[i]>0 ) { tmpb[i] = true; } else { tmpb[i] = false; } } cqmodels.cqmsetactiveset(a, sas.xc, tmpb); cqmodels.cqmsetq(a, sas.pbasis, tmp, sas.basissize, theta); // // Iterate until optimal values of Lagrange multipliers are found // for(i=0; i<=sas.basissize-1; i++) { lagrangec[i] = 0; } feaserrnew = math.maxrealnumber; result = true; for(itidx=1; itidx<=maxlagrangeits; itidx++) { // // Generate right part B using linear term and current // estimate of the Lagrange multipliers. // for(i_=0; i_<=n-1;i_++) { tmp[i_] = b[i_]; } for(i=0; i<=sas.basissize-1; i++) { v = lagrangec[i]; for(i_=0; i_<=n-1;i_++) { tmp[i_] = tmp[i_] - v*sas.pbasis[i,i_]; } } cqmodels.cqmsetb(a, tmp); // // Solve // result = cqmodels.cqmconstrainedoptimum(a, ref xn); if( !result ) { return result; } // // Compare feasibility errors. // Terminate if error decreased too slowly. // feaserrold = feaserrnew; feaserrnew = 0; for(i=0; i<=sas.basissize-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += sas.pbasis[i,i_]*xn[i_]; } feaserrnew = feaserrnew+math.sqr(v-sas.pbasis[i,n]); } feaserrnew = Math.Sqrt(feaserrnew); if( (double)(feaserrnew)>=(double)(0.2*feaserrold) ) { break; } // // Update Lagrange multipliers // for(i=0; i<=sas.basissize-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += sas.pbasis[i,i_]*xn[i_]; } lagrangec[i] = lagrangec[i]-theta*(v-sas.pbasis[i,n]); } } return result; }
/************************************************************************* Model value: f = 0.5*x'*A*x + b'*x INPUT PARAMETERS: A - convex quadratic model; only main quadratic term is used, other parts of the model (D/Q/linear term) are ignored. This function does not modify model state. B - right part XC - evaluation point Tmp - temporary buffer, automatically resized if needed -- ALGLIB -- Copyright 20.06.2012 by Bochkanov Sergey *************************************************************************/ private static double modelvalue(cqmodels.convexquadraticmodel a, double[] b, double[] xc, int n, ref double[] tmp) { double result = 0; double v0 = 0; double v1 = 0; int i_ = 0; apserv.rvectorsetlengthatleast(ref tmp, n); cqmodels.cqmadx(a, xc, ref tmp); v0 = 0.0; for(i_=0; i_<=n-1;i_++) { v0 += xc[i_]*tmp[i_]; } v1 = 0.0; for(i_=0; i_<=n-1;i_++) { v1 += xc[i_]*b[i_]; } result = 0.5*v0+v1; return result; }
/************************************************************************* This function runs QPCholesky solver; it returns after optimization process was completed. Following QP problem is solved: min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) subject to boundary constraints. INPUT PARAMETERS: AC - for dense problems (AKind=0) contains system matrix in the A-term of CQM object. OTHER TERMS ARE ACTIVELY USED AND MODIFIED BY THE SOLVER! SparseAC - for sparse problems (AKind=1 AKind - sparse matrix format: * 0 for dense matrix * 1 for sparse matrix SparseUpper - which triangle of SparseAC stores matrix - upper or lower one (for dense matrices this parameter is not actual). BC - linear term, array[NC] BndLC - lower bound, array[NC] BndUC - upper bound, array[NC] SC - scale vector, array[NC]: * I-th element contains scale of I-th variable, * SC[I]>0 XOriginC - origin term, array[NC]. Can be zero. NC - number of variables in the original formulation (no slack variables). CLEICC - linear equality/inequality constraints. Present version of this function does NOT provide publicly available support for linear constraints. This feature will be introduced in the future versions of the function. NEC, NIC - number of equality/inequality constraints. MUST BE ZERO IN THE CURRENT VERSION!!! Settings - QPCholeskySettings object initialized by one of the initialization functions. SState - object which stores temporaries: * if uninitialized object was passed, FirstCall parameter MUST be set to True; object will be automatically initialized by the function, and FirstCall will be set to False. * if FirstCall=False, it is assumed that this parameter was already initialized by previous call to this function with same problem dimensions (variable count N). XS - initial point, array[NC] OUTPUT PARAMETERS: XS - last point TerminationType-termination type: * * * -- ALGLIB -- Copyright 14.05.2011 by Bochkanov Sergey *************************************************************************/ public static void qpcholeskyoptimize(cqmodels.convexquadraticmodel a, double anorm, double[] b, double[] bndl, double[] bndu, double[] s, double[] xorigin, int n, double[,] cleic, int nec, int nic, qpcholeskybuffers sstate, ref double[] xsc, ref int terminationtype) { int i = 0; double noisetolerance = 0; bool havebc = new bool(); double v = 0; int badnewtonits = 0; double maxscaledgrad = 0; double v0 = 0; double v1 = 0; int nextaction = 0; double fprev = 0; double fcur = 0; double fcand = 0; double noiselevel = 0; double d0 = 0; double d1 = 0; double d2 = 0; int actstatus = 0; int i_ = 0; terminationtype = 0; // // Allocate storage and prepare fields // apserv.rvectorsetlengthatleast(ref sstate.rctmpg, n); apserv.rvectorsetlengthatleast(ref sstate.tmp0, n); apserv.rvectorsetlengthatleast(ref sstate.tmp1, n); apserv.rvectorsetlengthatleast(ref sstate.gc, n); apserv.rvectorsetlengthatleast(ref sstate.pg, n); apserv.rvectorsetlengthatleast(ref sstate.xs, n); apserv.rvectorsetlengthatleast(ref sstate.xn, n); apserv.rvectorsetlengthatleast(ref sstate.workbndl, n); apserv.rvectorsetlengthatleast(ref sstate.workbndu, n); apserv.bvectorsetlengthatleast(ref sstate.havebndl, n); apserv.bvectorsetlengthatleast(ref sstate.havebndu, n); sstate.repinneriterationscount = 0; sstate.repouteriterationscount = 0; sstate.repncholesky = 0; noisetolerance = 10; // // Our formulation of quadratic problem includes origin point, // i.e. we have F(x-x_origin) which is minimized subject to // constraints on x, instead of having simply F(x). // // Here we make transition from non-zero origin to zero one. // In order to make such transition we have to: // 1. subtract x_origin from x_start // 2. modify constraints // 3. solve problem // 4. add x_origin to solution // // There is alternate solution - to modify quadratic function // by expansion of multipliers containing (x-x_origin), but // we prefer to modify constraints, because it is a) more precise // and b) easier to to. // // Parts (1)-(2) are done here. After this block is over, // we have: // * XS, which stores shifted XStart (if we don't have XStart, // value of XS will be ignored later) // * WorkBndL, WorkBndU, which store modified boundary constraints. // havebc = false; for(i=0; i<=n-1; i++) { sstate.havebndl[i] = math.isfinite(bndl[i]); sstate.havebndu[i] = math.isfinite(bndu[i]); havebc = (havebc || sstate.havebndl[i]) || sstate.havebndu[i]; if( sstate.havebndl[i] ) { sstate.workbndl[i] = bndl[i]-xorigin[i]; } else { sstate.workbndl[i] = Double.NegativeInfinity; } if( sstate.havebndu[i] ) { sstate.workbndu[i] = bndu[i]-xorigin[i]; } else { sstate.workbndu[i] = Double.PositiveInfinity; } } apserv.rmatrixsetlengthatleast(ref sstate.workcleic, nec+nic, n+1); for(i=0; i<=nec+nic-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += cleic[i,i_]*xorigin[i_]; } for(i_=0; i_<=n-1;i_++) { sstate.workcleic[i,i_] = cleic[i,i_]; } sstate.workcleic[i,n] = cleic[i,n]-v; } // // We have starting point in StartX, so we just have to shift and bound it // for(i=0; i<=n-1; i++) { sstate.xs[i] = xsc[i]-xorigin[i]; if( sstate.havebndl[i] ) { if( (double)(sstate.xs[i])<(double)(sstate.workbndl[i]) ) { sstate.xs[i] = sstate.workbndl[i]; } } if( sstate.havebndu[i] ) { if( (double)(sstate.xs[i])>(double)(sstate.workbndu[i]) ) { sstate.xs[i] = sstate.workbndu[i]; } } } // // Handle special case - no constraints // if( !havebc && nec+nic==0 ) { // // "Simple" unconstrained Cholesky // apserv.bvectorsetlengthatleast(ref sstate.tmpb, n); for(i=0; i<=n-1; i++) { sstate.tmpb[i] = false; } sstate.repncholesky = sstate.repncholesky+1; cqmodels.cqmsetb(a, b); cqmodels.cqmsetactiveset(a, sstate.xs, sstate.tmpb); if( !cqmodels.cqmconstrainedoptimum(a, ref sstate.xn) ) { terminationtype = -5; return; } for(i_=0; i_<=n-1;i_++) { xsc[i_] = sstate.xn[i_]; } for(i_=0; i_<=n-1;i_++) { xsc[i_] = xsc[i_] + xorigin[i_]; } sstate.repinneriterationscount = 1; sstate.repouteriterationscount = 1; terminationtype = 4; return; } // // Prepare "active set" structure // sactivesets.sasinit(n, sstate.sas); sactivesets.sassetbc(sstate.sas, sstate.workbndl, sstate.workbndu); sactivesets.sassetlcx(sstate.sas, sstate.workcleic, nec, nic); sactivesets.sassetscale(sstate.sas, s); if( !sactivesets.sasstartoptimization(sstate.sas, sstate.xs) ) { terminationtype = -3; return; } // // Main cycle of CQP algorithm // terminationtype = 4; badnewtonits = 0; maxscaledgrad = 0.0; while( true ) { // // Update iterations count // apserv.inc(ref sstate.repouteriterationscount); apserv.inc(ref sstate.repinneriterationscount); // // Phase 1. // // Determine active set. // Update MaxScaledGrad. // cqmodels.cqmadx(a, sstate.sas.xc, ref sstate.rctmpg); for(i_=0; i_<=n-1;i_++) { sstate.rctmpg[i_] = sstate.rctmpg[i_] + b[i_]; } sactivesets.sasreactivateconstraints(sstate.sas, sstate.rctmpg); v = 0.0; for(i=0; i<=n-1; i++) { v = v+math.sqr(sstate.rctmpg[i]*s[i]); } maxscaledgrad = Math.Max(maxscaledgrad, Math.Sqrt(v)); // // Phase 2: perform penalized steepest descent step. // // NextAction control variable is set on exit from this loop: // * NextAction>0 in case we have to proceed to Phase 3 (Newton step) // * NextAction<0 in case we have to proceed to Phase 1 (recalculate active set) // * NextAction=0 in case we found solution (step along projected gradient is small enough) // while( true ) { // // Calculate constrained descent direction, store to PG. // Successful termination if PG is zero. // cqmodels.cqmadx(a, sstate.sas.xc, ref sstate.gc); for(i_=0; i_<=n-1;i_++) { sstate.gc[i_] = sstate.gc[i_] + b[i_]; } sactivesets.sasconstraineddescent(sstate.sas, sstate.gc, ref sstate.pg); v0 = 0.0; for(i_=0; i_<=n-1;i_++) { v0 += sstate.pg[i_]*sstate.pg[i_]; } if( (double)(v0)==(double)(0) ) { // // Constrained derivative is zero. // Solution found. // nextaction = 0; break; } // // Build quadratic model of F along descent direction: // F(xc+alpha*pg) = D2*alpha^2 + D1*alpha + D0 // Store noise level in the XC (noise level is used to classify // step as singificant or insignificant). // // In case function curvature is negative or product of descent // direction and gradient is non-negative, iterations are terminated. // // NOTE: D0 is not actually used, but we prefer to maintain it. // fprev = modelvalue(a, b, sstate.sas.xc, n, ref sstate.tmp0); fprev = fprev+penaltyfactor*maxscaledgrad*sactivesets.sasactivelcpenalty1(sstate.sas, sstate.sas.xc); cqmodels.cqmevalx(a, sstate.sas.xc, ref v, ref noiselevel); v0 = cqmodels.cqmxtadx2(a, sstate.pg); d2 = v0; v1 = 0.0; for(i_=0; i_<=n-1;i_++) { v1 += sstate.pg[i_]*sstate.gc[i_]; } d1 = v1; d0 = fprev; if( (double)(d2)<=(double)(0) ) { // // Second derivative is non-positive, function is non-convex. // terminationtype = -5; nextaction = 0; break; } if( (double)(d1)>=(double)(0) ) { // // Second derivative is positive, first derivative is non-negative. // Solution found. // nextaction = 0; break; } // // Modify quadratic model - add penalty for violation of the active // constraints. // // Boundary constraints are always satisfied exactly, so we do not // add penalty term for them. General equality constraint of the // form a'*(xc+alpha*d)=b adds penalty term: // P(alpha) = (a'*(xc+alpha*d)-b)^2 // = (alpha*(a'*d) + (a'*xc-b))^2 // = alpha^2*(a'*d)^2 + alpha*2*(a'*d)*(a'*xc-b) + (a'*xc-b)^2 // Each penalty term is multiplied by 100*Anorm before adding it to // the 1-dimensional quadratic model. // // Penalization of the quadratic model improves behavior of the // algorithm in the presense of the multiple degenerate constraints. // In particular, it prevents algorithm from making large steps in // directions which violate equality constraints. // for(i=0; i<=nec+nic-1; i++) { if( sstate.sas.activeset[n+i]>0 ) { v0 = 0.0; for(i_=0; i_<=n-1;i_++) { v0 += sstate.workcleic[i,i_]*sstate.pg[i_]; } v1 = 0.0; for(i_=0; i_<=n-1;i_++) { v1 += sstate.workcleic[i,i_]*sstate.sas.xc[i_]; } v1 = v1-sstate.workcleic[i,n]; v = 100*anorm; d2 = d2+v*math.sqr(v0); d1 = d1+v*2*v0*v1; d0 = d0+v*math.sqr(v1); } } // // Try unbounded step. // In case function change is dominated by noise or function actually increased // instead of decreasing, we terminate iterations. // v = -(d1/(2*d2)); for(i_=0; i_<=n-1;i_++) { sstate.xn[i_] = sstate.sas.xc[i_]; } for(i_=0; i_<=n-1;i_++) { sstate.xn[i_] = sstate.xn[i_] + v*sstate.pg[i_]; } fcand = modelvalue(a, b, sstate.xn, n, ref sstate.tmp0); fcand = fcand+penaltyfactor*maxscaledgrad*sactivesets.sasactivelcpenalty1(sstate.sas, sstate.xn); if( (double)(fcand)>=(double)(fprev-noiselevel*noisetolerance) ) { nextaction = 0; break; } // // Save active set // Perform bounded step with (possible) activation // actstatus = boundedstepandactivation(sstate.sas, sstate.xn, n, ref sstate.tmp0); fcur = modelvalue(a, b, sstate.sas.xc, n, ref sstate.tmp0); // // Depending on results, decide what to do: // 1. In case step was performed without activation of constraints, // we proceed to Newton method // 2. In case there was activated at least one constraint with ActiveSet[I]<0, // we proceed to Phase 1 and re-evaluate active set. // 3. Otherwise (activation of the constraints with ActiveSet[I]=0) // we try Phase 2 one more time. // if( actstatus<0 ) { // // Step without activation, proceed to Newton // nextaction = 1; break; } if( actstatus==0 ) { // // No new constraints added during last activation - only // ones which were at the boundary (ActiveSet[I]=0), but // inactive due to numerical noise. // // Now, these constraints are added to the active set, and // we try to perform steepest descent (Phase 2) one more time. // continue; } else { // // Last step activated at least one significantly new // constraint (ActiveSet[I]<0), we have to re-evaluate // active set (Phase 1). // nextaction = -1; break; } } if( nextaction<0 ) { continue; } if( nextaction==0 ) { break; } // // Phase 3: fast equality-constrained solver // // NOTE: this solver uses Augmented Lagrangian algorithm to solve // equality-constrained subproblems. This algorithm may // perform steps which increase function values instead of // decreasing it (in hard cases, like overconstrained problems). // // Such non-monononic steps may create a loop, when Augmented // Lagrangian algorithm performs uphill step, and steepest // descent algorithm (Phase 2) performs downhill step in the // opposite direction. // // In order to prevent iterations to continue forever we // count iterations when AL algorithm increased function // value instead of decreasing it. When number of such "bad" // iterations will increase beyong MaxBadNewtonIts, we will // terminate algorithm. // fprev = modelvalue(a, b, sstate.sas.xc, n, ref sstate.tmp0); while( true ) { // // Calculate optimum subject to presently active constraints // sstate.repncholesky = sstate.repncholesky+1; if( !constrainedoptimum(sstate.sas, a, anorm, b, ref sstate.xn, n, ref sstate.tmp0, ref sstate.tmpb, ref sstate.tmp1) ) { terminationtype = -5; sactivesets.sasstopoptimization(sstate.sas); return; } // // Add constraints. // If no constraints was added, accept candidate point XN and move to next phase. // if( boundedstepandactivation(sstate.sas, sstate.xn, n, ref sstate.tmp0)<0 ) { break; } } fcur = modelvalue(a, b, sstate.sas.xc, n, ref sstate.tmp0); if( (double)(fcur)>=(double)(fprev) ) { badnewtonits = badnewtonits+1; } if( badnewtonits>=maxbadnewtonits ) { // // Algorithm found solution, but keeps iterating because Newton // algorithm performs uphill steps (noise in the Augmented Lagrangian // algorithm). We terminate algorithm; it is considered normal // termination. // break; } } sactivesets.sasstopoptimization(sstate.sas); // // Post-process: add XOrigin to XC // for(i=0; i<=n-1; i++) { if( sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.workbndl[i]) ) { xsc[i] = bndl[i]; continue; } if( sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.workbndu[i]) ) { xsc[i] = bndu[i]; continue; } xsc[i] = apserv.boundval(sstate.sas.xc[i]+xorigin[i], bndl[i], bndu[i]); } }
/************************************************************************* This function runs QPBLEIC solver; it returns after optimization process was completed. Following QP problem is solved: min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) subject to boundary constraints. INPUT PARAMETERS: AC - for dense problems (AKind=0), A-term of CQM object contains system matrix. Other terms are unspecified and should not be referenced. SparseAC - for sparse problems (AKind=1 AKind - sparse matrix format: * 0 for dense matrix * 1 for sparse matrix SparseUpper - which triangle of SparseAC stores matrix - upper or lower one (for dense matrices this parameter is not actual). AbsASum - SUM(|A[i,j]|) AbsASum2 - SUM(A[i,j]^2) BC - linear term, array[NC] BndLC - lower bound, array[NC] BndUC - upper bound, array[NC] SC - scale vector, array[NC]: * I-th element contains scale of I-th variable, * SC[I]>0 XOriginC - origin term, array[NC]. Can be zero. NC - number of variables in the original formulation (no slack variables). CLEICC - linear equality/inequality constraints. Present version of this function does NOT provide publicly available support for linear constraints. This feature will be introduced in the future versions of the function. NEC, NIC - number of equality/inequality constraints. MUST BE ZERO IN THE CURRENT VERSION!!! Settings - QPBLEICSettings object initialized by one of the initialization functions. SState - object which stores temporaries: * if uninitialized object was passed, FirstCall parameter MUST be set to True; object will be automatically initialized by the function, and FirstCall will be set to False. * if FirstCall=False, it is assumed that this parameter was already initialized by previous call to this function with same problem dimensions (variable count N). FirstCall - whether it is first call of this function for this specific instance of SState, with this number of variables N specified. XS - initial point, array[NC] OUTPUT PARAMETERS: XS - last point FirstCall - uncondtionally set to False TerminationType-termination type: * * * -- ALGLIB -- Copyright 14.05.2011 by Bochkanov Sergey *************************************************************************/ public static void qpbleicoptimize(cqmodels.convexquadraticmodel a, sparse.sparsematrix sparsea, int akind, bool sparseaupper, double absasum, double absasum2, double[] b, double[] bndl, double[] bndu, double[] s, double[] xorigin, int n, double[,] cleic, int nec, int nic, qpbleicsettings settings, qpbleicbuffers sstate, ref bool firstcall, ref double[] xs, ref int terminationtype) { int i = 0; double d2 = 0; double d1 = 0; double d0 = 0; double v = 0; double v0 = 0; double v1 = 0; double md = 0; double mx = 0; double mb = 0; int d1est = 0; int d2est = 0; int i_ = 0; terminationtype = 0; alglib.ap.assert(akind==0 || akind==1, "QPBLEICOptimize: unexpected AKind"); sstate.repinneriterationscount = 0; sstate.repouteriterationscount = 0; terminationtype = 0; // // Prepare solver object, if needed // if( firstcall ) { minbleic.minbleiccreate(n, xs, sstate.solver); firstcall = false; } // // Prepare max(|B|) // mb = 0.0; for(i=0; i<=n-1; i++) { mb = Math.Max(mb, Math.Abs(b[i])); } // // Temporaries // apserv.ivectorsetlengthatleast(ref sstate.tmpi, nec+nic); apserv.rvectorsetlengthatleast(ref sstate.tmp0, n); apserv.rvectorsetlengthatleast(ref sstate.tmp1, n); for(i=0; i<=nec-1; i++) { sstate.tmpi[i] = 0; } for(i=0; i<=nic-1; i++) { sstate.tmpi[nec+i] = -1; } minbleic.minbleicsetlc(sstate.solver, cleic, sstate.tmpi, nec+nic); minbleic.minbleicsetbc(sstate.solver, bndl, bndu); minbleic.minbleicsetdrep(sstate.solver, true); minbleic.minbleicsetcond(sstate.solver, math.minrealnumber, 0.0, 0.0, settings.maxits); minbleic.minbleicsetscale(sstate.solver, s); minbleic.minbleicsetprecscale(sstate.solver); minbleic.minbleicrestartfrom(sstate.solver, xs); while( minbleic.minbleiciteration(sstate.solver) ) { // // Line search started // if( sstate.solver.lsstart ) { // // Iteration counters: // * inner iterations count is increased on every line search // * outer iterations count is increased only at steepest descent line search // apserv.inc(ref sstate.repinneriterationscount); if( sstate.solver.steepestdescentstep ) { apserv.inc(ref sstate.repouteriterationscount); } // // Build quadratic model of F along descent direction: // // F(x+alpha*d) = D2*alpha^2 + D1*alpha + D0 // // Calculate estimates of linear and quadratic term // (term magnitude is compared with magnitude of numerical errors) // d0 = sstate.solver.f; d1 = 0.0; for(i_=0; i_<=n-1;i_++) { d1 += sstate.solver.d[i_]*sstate.solver.g[i_]; } d2 = 0; if( akind==0 ) { d2 = cqmodels.cqmxtadx2(a, sstate.solver.d); } if( akind==1 ) { sparse.sparsesmv(sparsea, sparseaupper, sstate.solver.d, ref sstate.tmp0); d2 = 0.0; for(i=0; i<=n-1; i++) { d2 = d2+sstate.solver.d[i]*sstate.tmp0[i]; } d2 = 0.5*d2; } mx = 0.0; md = 0.0; for(i=0; i<=n-1; i++) { mx = Math.Max(mx, Math.Abs(sstate.solver.x[i])); md = Math.Max(md, Math.Abs(sstate.solver.d[i])); } optserv.estimateparabolicmodel(absasum, absasum2, mx, mb, md, d1, d2, ref d1est, ref d2est); // // Tests for "normal" convergence. // // This line search may be started from steepest descent // stage (stage 2) or from L-BFGS stage (stage 3) of the // BLEIC algorithm. Depending on stage type, different // checks are performed. // // Say, L-BFGS stage is an equality-constrained refinement // stage of BLEIC. This stage refines current iterate // under "frozen" equality constraints. We can terminate // iterations at this stage only when we encounter // unconstrained direction of negative curvature. In all // other cases (say, when constrained gradient is zero) // we should not terminate algorithm because everything may // change after de-activating presently active constraints. // // Tests for convergence are performed only at "steepest descent" stage // of the BLEIC algorithm, and only when function is non-concave // (D2 is positive or approximately zero) along direction D. // // NOTE: we do not test iteration count (MaxIts) here, because // this stopping condition is tested by BLEIC itself. // if( sstate.solver.steepestdescentstep && d2est>=0 ) { if( d1est>=0 ) { // // "Emergency" stopping condition: D is non-descent direction. // Sometimes it is possible because of numerical noise in the // target function. // terminationtype = 4; for(i=0; i<=n-1; i++) { xs[i] = sstate.solver.x[i]; } break; } if( d2est>0 ) { // // Stopping condition #4 - gradient norm is small: // // 1. rescale State.Solver.D and State.Solver.G according to // current scaling, store results to Tmp0 and Tmp1. // 2. Normalize Tmp0 (scaled direction vector). // 3. compute directional derivative (in scaled variables), // which is equal to DOTPRODUCT(Tmp0,Tmp1). // v = 0; for(i=0; i<=n-1; i++) { sstate.tmp0[i] = sstate.solver.d[i]/s[i]; sstate.tmp1[i] = sstate.solver.g[i]*s[i]; v = v+math.sqr(sstate.tmp0[i]); } alglib.ap.assert((double)(v)>(double)(0), "QPBLEICOptimize: inernal errror (scaled direction is zero)"); v = 1/Math.Sqrt(v); for(i_=0; i_<=n-1;i_++) { sstate.tmp0[i_] = v*sstate.tmp0[i_]; } v = 0.0; for(i_=0; i_<=n-1;i_++) { v += sstate.tmp0[i_]*sstate.tmp1[i_]; } if( (double)(Math.Abs(v))<=(double)(settings.epsg) ) { terminationtype = 4; for(i=0; i<=n-1; i++) { xs[i] = sstate.solver.x[i]; } break; } // // Stopping condition #1 - relative function improvement is small: // // 1. calculate steepest descent step: V = -D1/(2*D2) // 2. calculate function change: V1= D2*V^2 + D1*V // 3. stop if function change is small enough // v = -(d1/(2*d2)); v1 = d2*v*v+d1*v; if( (double)(Math.Abs(v1))<=(double)(settings.epsf*Math.Max(d0, 1.0)) ) { terminationtype = 1; for(i=0; i<=n-1; i++) { xs[i] = sstate.solver.x[i]; } break; } // // Stopping condition #2 - scaled step is small: // // 1. calculate step multiplier V0 (step itself is D*V0) // 2. calculate scaled step length V // 3. stop if step is small enough // v0 = -(d1/(2*d2)); v = 0; for(i=0; i<=n-1; i++) { v = v+math.sqr(v0*sstate.solver.d[i]/s[i]); } if( (double)(Math.Sqrt(v))<=(double)(settings.epsx) ) { terminationtype = 2; for(i=0; i<=n-1; i++) { xs[i] = sstate.solver.x[i]; } break; } } } // // Test for unconstrained direction of negative curvature // if( (d2est<0 || (d2est==0 && d1est<0)) && !sstate.solver.boundedstep ) { // // Function is unbounded from below: // * function will decrease along D, i.e. either: // * D2<0 // * D2=0 and D1<0 // * step is unconstrained // // If these conditions are true, we abnormally terminate QP // algorithm with return code -4 (we can do so at any stage // of BLEIC - whether it is L-BFGS or steepest descent one). // terminationtype = -4; for(i=0; i<=n-1; i++) { xs[i] = sstate.solver.x[i]; } break; } // // Suggest new step (only if D1 is negative far away from zero, // D2 is positive far away from zero). // if( d1est<0 && d2est>0 ) { sstate.solver.stp = apserv.safeminposrv(-d1, 2*d2, sstate.solver.curstpmax); } } // // Gradient evaluation // if( sstate.solver.needfg ) { for(i=0; i<=n-1; i++) { sstate.tmp0[i] = sstate.solver.x[i]-xorigin[i]; } if( akind==0 ) { cqmodels.cqmadx(a, sstate.tmp0, ref sstate.tmp1); } if( akind==1 ) { sparse.sparsesmv(sparsea, sparseaupper, sstate.tmp0, ref sstate.tmp1); } v0 = 0.0; for(i_=0; i_<=n-1;i_++) { v0 += sstate.tmp0[i_]*sstate.tmp1[i_]; } v1 = 0.0; for(i_=0; i_<=n-1;i_++) { v1 += sstate.tmp0[i_]*b[i_]; } sstate.solver.f = 0.5*v0+v1; for(i_=0; i_<=n-1;i_++) { sstate.solver.g[i_] = sstate.tmp1[i_]; } for(i_=0; i_<=n-1;i_++) { sstate.solver.g[i_] = sstate.solver.g[i_] + b[i_]; } } } if( terminationtype==0 ) { // // BLEIC optimizer was terminated by one of its inner stopping // conditions. Usually it is iteration counter (if such // stopping condition was specified by user). // minbleic.minbleicresultsbuf(sstate.solver, ref xs, sstate.solverrep); terminationtype = sstate.solverrep.terminationtype; } else { // // BLEIC optimizer was terminated in "emergency" mode by QP // solver. // // NOTE: such termination is "emergency" only when viewed from // BLEIC's position. QP solver sees such termination as // routine one, triggered by QP's stopping criteria. // minbleic.minbleicemergencytermination(sstate.solver); } }
/************************************************************************* This function runs QQP solver; it returns after optimization process was completed. Following QP problem is solved: min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) subject to boundary constraints. IMPORTANT: UNLIKE MANY OTHER SOLVERS, THIS FUNCTION DOES NOT REQUIRE YOU TO INITIALIZE STATE OBJECT. IT CAN BE AUTOMATICALLY INITIALIZED DURING SOLUTION PROCESS. INPUT PARAMETERS: AC - for dense problems (AKind=0) A-term of CQM object contains system matrix. Other terms are unspecified and should not be referenced. SparseAC - for sparse problems (AKind=1 AKind - sparse matrix format: * 0 for dense matrix * 1 for sparse matrix SparseUpper - which triangle of SparseAC stores matrix - upper or lower one (for dense matrices this parameter is not actual). BC - linear term, array[NC] BndLC - lower bound, array[NC] BndUC - upper bound, array[NC] SC - scale vector, array[NC]: * I-th element contains scale of I-th variable, * SC[I]>0 XOriginC - origin term, array[NC]. Can be zero. NC - number of variables in the original formulation (no slack variables). CLEICC - linear equality/inequality constraints. Present version of this function does NOT provide publicly available support for linear constraints. This feature will be introduced in the future versions of the function. NEC, NIC - number of equality/inequality constraints. MUST BE ZERO IN THE CURRENT VERSION!!! Settings - QQPSettings object initialized by one of the initialization functions. SState - object which stores temporaries: * uninitialized object is automatically initialized * previously allocated memory is reused as much as possible XS - initial point, array[NC] OUTPUT PARAMETERS: XS - last point TerminationType-termination type: * * * -- ALGLIB -- Copyright 14.05.2011 by Bochkanov Sergey *************************************************************************/ public static void qqpoptimize(cqmodels.convexquadraticmodel ac, sparse.sparsematrix sparseac, int akind, bool sparseupper, double[] bc, double[] bndlc, double[] bnduc, double[] sc, double[] xoriginc, int nc, double[,] cleicc, int nec, int nic, qqpsettings settings, qqpbuffers sstate, double[] xs, ref int terminationtype) { int n = 0; int nmain = 0; int i = 0; int j = 0; int k = 0; double v = 0; double vv = 0; double d2 = 0; double d1 = 0; int d1est = 0; int d2est = 0; bool needact = new bool(); double reststp = 0; double fullstp = 0; double stpmax = 0; double stp = 0; int stpcnt = 0; int cidx = 0; double cval = 0; int cgcnt = 0; int cgmax = 0; int newtcnt = 0; int sparsesolver = 0; double beta = 0; bool b = new bool(); double fprev = 0; double fcur = 0; int i_ = 0; terminationtype = 0; // // Primary checks // alglib.ap.assert(akind==0 || akind==1, "QQPOptimize: incorrect AKind"); sstate.nmain = nc; sstate.nslack = nic; sstate.n = nc+nic; sstate.nec = nec; sstate.nic = nic; sstate.akind = akind; n = sstate.n; nmain = sstate.nmain; terminationtype = 0; sstate.repinneriterationscount = 0; sstate.repouteriterationscount = 0; sstate.repncholesky = 0; sstate.repncupdates = 0; // // Several checks // * matrix size // * scale vector // * consistency of bound constraints // * consistency of settings // if( akind==1 ) { alglib.ap.assert(sparse.sparsegetnrows(sparseac)==nmain, "QQPOptimize: rows(SparseAC)<>NMain"); alglib.ap.assert(sparse.sparsegetncols(sparseac)==nmain, "QQPOptimize: cols(SparseAC)<>NMain"); } for(i=0; i<=nmain-1; i++) { alglib.ap.assert(math.isfinite(sc[i]) && (double)(sc[i])>(double)(0), "QQPOptimize: incorrect scale"); } for(i=0; i<=nmain-1; i++) { if( math.isfinite(bndlc[i]) && math.isfinite(bnduc[i]) ) { if( (double)(bndlc[i])>(double)(bnduc[i]) ) { terminationtype = -3; return; } } } alglib.ap.assert(settings.cgphase || settings.cnphase, "QQPOptimize: both phases (CG and Newton) are inactive"); // // Allocate data structures // apserv.rvectorsetlengthatleast(ref sstate.bndl, n); apserv.rvectorsetlengthatleast(ref sstate.bndu, n); apserv.bvectorsetlengthatleast(ref sstate.havebndl, n); apserv.bvectorsetlengthatleast(ref sstate.havebndu, n); apserv.rvectorsetlengthatleast(ref sstate.xs, n); apserv.rvectorsetlengthatleast(ref sstate.xp, n); apserv.rvectorsetlengthatleast(ref sstate.gc, n); apserv.rvectorsetlengthatleast(ref sstate.cgc, n); apserv.rvectorsetlengthatleast(ref sstate.cgp, n); apserv.rvectorsetlengthatleast(ref sstate.dc, n); apserv.rvectorsetlengthatleast(ref sstate.dp, n); apserv.rvectorsetlengthatleast(ref sstate.tmp0, n); apserv.rvectorsetlengthatleast(ref sstate.stpbuf, 15); sactivesets.sasinit(n, sstate.sas); // // Scale/shift problem coefficients: // // min { 0.5*(x-x0)'*A*(x-x0) + b'*(x-x0) } // // becomes (after transformation "x = S*y+x0") // // min { 0.5*y'*(S*A*S)*y + (S*b)'*y // // Modified A_mod=S*A*S and b_mod=S*(b+A*x0) are // stored into SState.DenseA and SState.B. // // NOTE: DenseA/DenseB are arrays whose lengths are // NMain, not N=NMain+NSlack! We store reduced // matrix and vector because extend parts (last // NSlack rows/columns) are exactly zero. // // apserv.rvectorsetlengthatleast(ref sstate.b, nmain); for(i=0; i<=nmain-1; i++) { sstate.b[i] = sc[i]*bc[i]; } if( akind==0 ) { // // Dense QP problem - just copy and scale. // apserv.rmatrixsetlengthatleast(ref sstate.densea, nmain, nmain); cqmodels.cqmgeta(ac, ref sstate.densea); sstate.absamax = 0; sstate.absasum = 0; sstate.absasum2 = 0; for(i=0; i<=nmain-1; i++) { for(j=0; j<=nmain-1; j++) { v = sc[i]*sstate.densea[i,j]*sc[j]; sstate.densea[i,j] = v; sstate.absamax = Math.Max(sstate.absamax, v); sstate.absasum = sstate.absasum+v; sstate.absasum2 = sstate.absasum2+v*v; } } } else { // // Sparse QP problem - a bit tricky. Depending on format of the // input we use different strategies for copying matrix: // * SKS matrices are copied to SKS format // * anything else is copied to CRS format // alglib.ap.assert(akind==1, "QQPOptimize: unexpected AKind (internal error)"); sparse.sparsecopytosksbuf(sparseac, sstate.sparsea); if( sparseupper ) { sparse.sparsetransposesks(sstate.sparsea); } sstate.sparseupper = false; sstate.absamax = 0; sstate.absasum = 0; sstate.absasum2 = 0; for(i=0; i<=n-1; i++) { k = sstate.sparsea.ridx[i]; for(j=i-sstate.sparsea.didx[i]; j<=i; j++) { v = sc[i]*sstate.sparsea.vals[k]*sc[j]; sstate.sparsea.vals[k] = v; if( i==j ) { // // Diagonal terms are counted only once // sstate.absamax = Math.Max(sstate.absamax, v); sstate.absasum = sstate.absasum+v; sstate.absasum2 = sstate.absasum2+v*v; } else { // // Offdiagonal terms are counted twice // sstate.absamax = Math.Max(sstate.absamax, v); sstate.absasum = sstate.absasum+2*v; sstate.absasum2 = sstate.absasum2+2*v*v; } k = k+1; } } } // // Load box constraints into State structure. // // We apply transformation to variables: y=(x-x_origin)/s, // each of the constraints is appropriately shifted/scaled. // for(i=0; i<=nmain-1; i++) { sstate.havebndl[i] = math.isfinite(bndlc[i]); if( sstate.havebndl[i] ) { sstate.bndl[i] = (bndlc[i]-xoriginc[i])/sc[i]; } else { alglib.ap.assert(Double.IsNegativeInfinity(bndlc[i]), "QQPOptimize: incorrect lower bound"); sstate.bndl[i] = Double.NegativeInfinity; } sstate.havebndu[i] = math.isfinite(bnduc[i]); if( sstate.havebndu[i] ) { sstate.bndu[i] = (bnduc[i]-xoriginc[i])/sc[i]; } else { alglib.ap.assert(Double.IsPositiveInfinity(bnduc[i]), "QQPOptimize: incorrect upper bound"); sstate.bndu[i] = Double.PositiveInfinity; } } for(i=nmain; i<=n-1; i++) { sstate.havebndl[i] = true; sstate.bndl[i] = 0.0; sstate.havebndu[i] = false; sstate.bndu[i] = Double.PositiveInfinity; } // // Shift/scale linear constraints with transformation y=(x-x_origin)/s: // * constraint "c[i]'*x = b[i]" becomes "(s[i]*c[i])'*x = b[i]-c[i]'*x_origin". // * after constraint is loaded into SState.CLEIC, it is additionally normalized // apserv.rmatrixsetlengthatleast(ref sstate.cleic, nec+nic, n+1); for(i=0; i<=nec+nic-1; i++) { v = 0; vv = 0; for(j=0; j<=nmain-1; j++) { sstate.cleic[i,j] = cleicc[i,j]*sc[j]; vv = vv+math.sqr(sstate.cleic[i,j]); v = v+cleicc[i,j]*xoriginc[j]; } vv = Math.Sqrt(vv); for(j=nmain; j<=n-1; j++) { sstate.cleic[i,j] = 0.0; } sstate.cleic[i,n] = cleicc[i,nmain]-v; if( i>=nec ) { sstate.cleic[i,nmain+i-nec] = 1.0; } if( (double)(vv)>(double)(0) ) { for(j=0; j<=n; j++) { sstate.cleic[i,j] = sstate.cleic[i,j]/vv; } } } // // Process initial point: // * first NMain components are equal to XS-XOriginC // * last NIC components are deduced from linear constraints // * make sure that boundary constraints are preserved by transformation // for(i=0; i<=nmain-1; i++) { sstate.xs[i] = (xs[i]-xoriginc[i])/sc[i]; if( sstate.havebndl[i] && (double)(sstate.xs[i])<(double)(sstate.bndl[i]) ) { sstate.xs[i] = sstate.bndl[i]; } if( sstate.havebndu[i] && (double)(sstate.xs[i])>(double)(sstate.bndu[i]) ) { sstate.xs[i] = sstate.bndu[i]; } if( sstate.havebndl[i] && (double)(xs[i])==(double)(bndlc[i]) ) { sstate.xs[i] = sstate.bndl[i]; } if( sstate.havebndu[i] && (double)(xs[i])==(double)(bnduc[i]) ) { sstate.xs[i] = sstate.bndu[i]; } } for(i=0; i<=nic-1; i++) { v = 0.0; for(i_=0; i_<=nmain-1;i_++) { v += sstate.xs[i_]*sstate.cleic[nec+i,i_]; } sstate.xs[nmain+i] = Math.Max(sstate.cleic[nec+i,n]-v, 0.0); } // // Prepare "active set" structure // sactivesets.sassetbc(sstate.sas, sstate.bndl, sstate.bndu); sactivesets.sassetlcx(sstate.sas, sstate.cleic, 0, 0); if( !sactivesets.sasstartoptimization(sstate.sas, sstate.xs) ) { terminationtype = -3; return; } // // Select sparse direct solver // if( akind==1 ) { sparsesolver = settings.sparsesolver; if( sparsesolver==0 ) { sparsesolver = 1; } if( sparse.sparseissks(sstate.sparsea) ) { sparsesolver = 2; } sparsesolver = 2; alglib.ap.assert(sparsesolver==1 || sparsesolver==2, "QQPOptimize: incorrect SparseSolver"); } else { sparsesolver = 0; } // // Main loop. // // Following variables are used: // * GC stores current gradient (unconstrained) // * CGC stores current gradient (constrained) // * DC stores current search direction // * CGP stores constrained gradient at previous point // (zero on initial entry) // * DP stores previous search direction // (zero on initial entry) // cgmax = settings.cgminits; sstate.repinneriterationscount = 0; sstate.repouteriterationscount = 0; while( true ) { if( settings.maxouterits>0 && sstate.repouteriterationscount>=settings.maxouterits ) { terminationtype = 5; break; } if( sstate.repouteriterationscount>0 ) { // // Check EpsF- and EpsX-based stopping criteria. // Because problem was already scaled, we do not scale step before checking its length. // NOTE: these checks are performed only after at least one outer iteration was made. // if( (double)(settings.epsf)>(double)(0) ) { // // NOTE 1: here we rely on the fact that ProjectedTargetFunction() ignore D when Stp=0 // NOTE 2: code below handles situation when update increases function value instead // of decreasing it. // fprev = projectedtargetfunction(sstate, sstate.xp, sstate.dc, 0.0, ref sstate.tmp0); fcur = projectedtargetfunction(sstate, sstate.sas.xc, sstate.dc, 0.0, ref sstate.tmp0); if( (double)(fprev-fcur)<=(double)(settings.epsf*Math.Max(Math.Abs(fprev), Math.Max(Math.Abs(fcur), 1.0))) ) { terminationtype = 1; break; } } if( (double)(settings.epsx)>(double)(0) ) { v = 0.0; for(i=0; i<=n-1; i++) { v = v+math.sqr(sstate.xp[i]-sstate.sas.xc[i]); } if( (double)(Math.Sqrt(v))<=(double)(settings.epsx) ) { terminationtype = 2; break; } } } apserv.inc(ref sstate.repouteriterationscount); for(i_=0; i_<=n-1;i_++) { sstate.xp[i_] = sstate.sas.xc[i_]; } if( !settings.cgphase ) { cgmax = 0; } for(i=0; i<=n-1; i++) { sstate.cgp[i] = 0.0; sstate.dp[i] = 0.0; } for(cgcnt=0; cgcnt<=cgmax-1; cgcnt++) { // // Calculate unconstrained gradient GC for "extended" QP problem // Determine active set, current constrained gradient CGC. // Check gradient-based stopping condition. // // NOTE: because problem was scaled, we do not have to apply scaling // to gradient before checking stopping condition. // targetgradient(sstate, sstate.sas.xc, ref sstate.gc); sactivesets.sasreactivateconstraints(sstate.sas, sstate.gc); for(i_=0; i_<=n-1;i_++) { sstate.cgc[i_] = sstate.gc[i_]; } sactivesets.sasconstraineddirection(sstate.sas, ref sstate.cgc); v = 0.0; for(i_=0; i_<=n-1;i_++) { v += sstate.cgc[i_]*sstate.cgc[i_]; } if( (double)(Math.Sqrt(v))<=(double)(settings.epsg) ) { terminationtype = 4; break; } // // Prepare search direction DC and explore it. // // We try to use CGP/DP to prepare conjugate gradient step, // but we resort to steepest descent step (Beta=0) in case // we are at I-th boundary, but DP[I]<>0. // // Such approach allows us to ALWAYS have feasible DC, with // guaranteed compatibility with both feasible area and current // active set. // // Automatic CG reset performed every time DP is incompatible // with current active set and/or feasible area. We also // perform reset every QuickQPRestartCG iterations. // for(i_=0; i_<=n-1;i_++) { sstate.dc[i_] = -sstate.cgc[i_]; } v = 0.0; vv = 0.0; b = false; for(i=0; i<=n-1; i++) { v = v+sstate.cgc[i]*sstate.cgc[i]; vv = vv+sstate.cgp[i]*sstate.cgp[i]; b = b || ((sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndl[i])) && (double)(sstate.dp[i])!=(double)(0)); b = b || ((sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndu[i])) && (double)(sstate.dp[i])!=(double)(0)); } b = b || (double)(vv)==(double)(0); b = b || cgcnt%quickqprestartcg==0; if( !b ) { beta = v/vv; } else { beta = 0.0; } for(i_=0; i_<=n-1;i_++) { sstate.dc[i_] = sstate.dc[i_] + beta*sstate.dp[i_]; } sactivesets.sasconstraineddirection(sstate.sas, ref sstate.dc); sactivesets.sasexploredirection(sstate.sas, sstate.dc, ref stpmax, ref cidx, ref cval); // // Build quadratic model of F along descent direction: // // F(xc+alpha*D) = D2*alpha^2 + D1*alpha // // Terminate algorithm if needed. // // NOTE: we do not maintain constant term D0 // quadraticmodel(sstate, sstate.sas.xc, sstate.dc, sstate.gc, ref d1, ref d1est, ref d2, ref d2est); if( (double)(d1)==(double)(0) && (double)(d2)==(double)(0) ) { // // D1 and D2 are exactly zero, success. // After this if-then we assume that D is non-zero. // terminationtype = 4; break; } if( d1est>=0 ) { // // Numerical noise is too large, it means that we are close // to minimum - and that further improvement is impossible. // // After this if-then we assume that D1 is definitely negative // (even under presence of numerical errors). // terminationtype = 7; break; } if( d2est<=0 && cidx<0 ) { // // Function is unbounded from below: // * D1<0 (verified by previous block) // * D2Est<=0, which means that either D2<0 - or it can not // be reliably distinguished from zero. // * step is unconstrained // // If these conditions are true, we abnormally terminate QP // algorithm with return code -4 // terminationtype = -4; break; } // // Perform step along DC. // // In this block of code we maintain two step length: // * RestStp - restricted step, maximum step length along DC which does // not violate constraints // * FullStp - step length along DC which minimizes quadratic function // without taking constraints into account. If problem is // unbounded from below without constraints, FullStp is // forced to be RestStp. // // So, if function is convex (D2>0): // * FullStp = -D1/(2*D2) // * RestStp = restricted FullStp // * 0<=RestStp<=FullStp // // If function is non-convex, but bounded from below under constraints: // * RestStp = step length subject to constraints // * FullStp = RestStp // // After RestStp and FullStp are initialized, we generate several trial // steps which are different multiples of RestStp and FullStp. // if( d2est>0 ) { alglib.ap.assert((double)(d1)<(double)(0), "QQPOptimize: internal error"); fullstp = -(d1/(2*d2)); needact = (double)(fullstp)>=(double)(stpmax); if( needact ) { alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=3, "QQPOptimize: StpBuf overflow"); reststp = stpmax; stp = reststp; sstate.stpbuf[0] = reststp*4; sstate.stpbuf[1] = fullstp; sstate.stpbuf[2] = fullstp/4; stpcnt = 3; } else { reststp = fullstp; stp = fullstp; stpcnt = 0; } } else { alglib.ap.assert(cidx>=0, "QQPOptimize: internal error"); alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=2, "QQPOptimize: StpBuf overflow"); reststp = stpmax; fullstp = stpmax; stp = reststp; needact = true; sstate.stpbuf[0] = 4*reststp; stpcnt = 1; } findbeststepandmove(sstate, sstate.sas, sstate.dc, stp, needact, cidx, cval, sstate.stpbuf, stpcnt, ref sstate.activated, ref sstate.tmp0); // // Update CG information. // for(i_=0; i_<=n-1;i_++) { sstate.dp[i_] = sstate.dc[i_]; } for(i_=0; i_<=n-1;i_++) { sstate.cgp[i_] = sstate.cgc[i_]; } // // Update iterations counter // sstate.repinneriterationscount = sstate.repinneriterationscount+1; } if( terminationtype!=0 ) { break; } cgmax = settings.cgmaxits; // // Generate YIdx - reordering of variables for constrained Newton phase. // Free variables come first, fixed are last ones. // newtcnt = 0; while( true ) { // // Skip iteration if constrained Newton is turned off. // if( !settings.cnphase ) { break; } // // At the first iteration - build Cholesky decomposition of Hessian. // At subsequent iterations - refine Hessian by adding new constraints. // // Loop is terminated in following cases: // * Hessian is not positive definite subject to current constraints // (termination during initial decomposition) // * there were no new constraints being activated // (termination during update) // * all constraints were activated during last step // (termination during update) // * CNMaxUpdates were performed on matrix // (termination during update) // if( newtcnt==0 ) { // // Perform initial Newton step. If Cholesky decomposition fails, // increase number of CG iterations to CGMaxIts - it should help // us to find set of constraints which will make matrix positive // definite. // b = cnewtonbuild(sstate, sparsesolver, ref sstate.repncholesky); if( b ) { cgmax = settings.cgminits; } } else { b = cnewtonupdate(sstate, settings, ref sstate.repncupdates); } if( !b ) { break; } apserv.inc(ref newtcnt); // // Calculate gradient GC. // targetgradient(sstate, sstate.sas.xc, ref sstate.gc); // // Bound-constrained Newton step // for(i=0; i<=n-1; i++) { sstate.dc[i] = sstate.gc[i]; } if( !cnewtonstep(sstate, settings, sstate.dc) ) { break; } quadraticmodel(sstate, sstate.sas.xc, sstate.dc, sstate.gc, ref d1, ref d1est, ref d2, ref d2est); if( d1est>=0 || d2est<=0 ) { break; } alglib.ap.assert((double)(d1)<(double)(0), "QQPOptimize: internal error"); fullstp = -(d1/(2*d2)); sactivesets.sasexploredirection(sstate.sas, sstate.dc, ref stpmax, ref cidx, ref cval); needact = (double)(fullstp)>=(double)(stpmax); if( needact ) { alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=3, "QQPOptimize: StpBuf overflow"); reststp = stpmax; stp = reststp; sstate.stpbuf[0] = reststp*4; sstate.stpbuf[1] = fullstp; sstate.stpbuf[2] = fullstp/4; stpcnt = 3; } else { reststp = fullstp; stp = fullstp; stpcnt = 0; } findbeststepandmove(sstate, sstate.sas, sstate.dc, stp, needact, cidx, cval, sstate.stpbuf, stpcnt, ref sstate.activated, ref sstate.tmp0); } if( terminationtype!=0 ) { break; } } // // Stop optimization and unpack results. // // Add XOriginC to XS and make sure that boundary constraints are // both (a) satisfied, (b) preserved. Former means that "shifted" // point is feasible, while latter means that point which was exactly // at the boundary before shift will be exactly at the boundary // after shift. // sactivesets.sasstopoptimization(sstate.sas); for(i=0; i<=nmain-1; i++) { xs[i] = sc[i]*sstate.sas.xc[i]+xoriginc[i]; if( sstate.havebndl[i] && (double)(xs[i])<(double)(bndlc[i]) ) { xs[i] = bndlc[i]; } if( sstate.havebndu[i] && (double)(xs[i])>(double)(bnduc[i]) ) { xs[i] = bnduc[i]; } if( sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndl[i]) ) { xs[i] = bndlc[i]; } if( sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndu[i]) ) { xs[i] = bnduc[i]; } } }
/************************************************************************* Optimum of A subject to: a) active boundary constraints (given by ActiveB[] and corresponding elements of XC) b) active linear constraints (given by C, R, LagrangeC) INPUT PARAMETERS: A - main quadratic term of the model; although structure may store linear and rank-K terms, these terms are ignored and rewritten by this function. ANorm - estimate of ||A|| (2-norm is used) B - array[N], linear term of the model ActiveB - array[N], active boundary constraints XC - array[N], constrained values C - equality constraints, array[K,N] R - array[K], right part of the equality constraints K - number of equality constraints XN - possibly preallocated buffer Tmp - temporary buffer (automatically resized) Tmp1 - temporary buffer (automatically resized) OUTPUT PARAMETERS: A - modified quadratic model (this function changes rank-K term and linear term of the model) LagrangeC- current estimate of the Lagrange coefficients XN - solution RESULT: True on success, False on failure (non-SPD model) -- ALGLIB -- Copyright 20.06.2012 by Bochkanov Sergey *************************************************************************/ private static bool minqpconstrainedoptimum(cqmodels.convexquadraticmodel a, double anorm, double[] b, bool[] activeb, double[] xc, int n, double[,] c, double[] r, int k, ref double[] xn, ref double[] tmp, ref double[] lagrangec) { bool result = new bool(); int itidx = 0; int i = 0; double v = 0; double feaserrold = 0; double feaserrnew = 0; double theta = 0; int i_ = 0; // // Prepare model // theta = 100.0*anorm; apserv.rvectorsetlengthatleast(ref tmp, n); apserv.rvectorsetlengthatleast(ref lagrangec, k); cqmodels.cqmsetactiveset(a, xc, activeb); cqmodels.cqmsetq(a, c, r, k, theta); // // Iterate until optimal values of Lagrange multipliers are found // for(i=0; i<=k-1; i++) { lagrangec[i] = 0; } feaserrnew = math.maxrealnumber; result = true; for(itidx=1; itidx<=maxlagrangeits; itidx++) { // // Generate right part B using linear term and current // estimate of the Lagrange multipliers. // for(i_=0; i_<=n-1;i_++) { tmp[i_] = b[i_]; } for(i=0; i<=k-1; i++) { v = lagrangec[i]; for(i_=0; i_<=n-1;i_++) { tmp[i_] = tmp[i_] - v*c[i,i_]; } } cqmodels.cqmsetb(a, tmp); // // Solve // result = cqmodels.cqmconstrainedoptimum(a, ref xn); if( !result ) { return result; } // // Compare feasibility errors. // Terminate if error decreased too slowly. // feaserrold = feaserrnew; feaserrnew = 0; for(i=0; i<=k-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += c[i,i_]*xn[i_]; } feaserrnew = feaserrnew+math.sqr(v-r[i]); } feaserrnew = Math.Sqrt(feaserrnew); if( (double)(feaserrnew)>=(double)(0.2*feaserrold) ) { break; } // // Update Lagrange multipliers // for(i=0; i<=k-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += c[i,i_]*xn[i_]; } lagrangec[i] = lagrangec[i]-theta*(v-r[i]); } } return result; }