/************************************************************************* 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]); } }
public override alglib.apobject make_copy() { qpcholeskybuffers _result = new qpcholeskybuffers(); _result.sas = (sactivesets.sactiveset)sas.make_copy(); _result.pg = (double[])pg.Clone(); _result.gc = (double[])gc.Clone(); _result.xs = (double[])xs.Clone(); _result.xn = (double[])xn.Clone(); _result.workbndl = (double[])workbndl.Clone(); _result.workbndu = (double[])workbndu.Clone(); _result.havebndl = (bool[])havebndl.Clone(); _result.havebndu = (bool[])havebndu.Clone(); _result.workcleic = (double[,])workcleic.Clone(); _result.rctmpg = (double[])rctmpg.Clone(); _result.tmp0 = (double[])tmp0.Clone(); _result.tmp1 = (double[])tmp1.Clone(); _result.tmpb = (bool[])tmpb.Clone(); _result.repinneriterationscount = repinneriterationscount; _result.repouteriterationscount = repouteriterationscount; _result.repncholesky = repncholesky; return _result; }