/************************************************************************* This subroutine evaluates x'*(0.5*alpha*A+tau*D)*x -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static double cqmxtadx2(convexquadraticmodel s, double[] x) { double result = 0; int n = 0; int i = 0; int j = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMEval: X is not finite vector"); result = 0.0; // // main quadratic term // if( (double)(s.alpha)>(double)(0) ) { for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { result = result+s.alpha*0.5*x[i]*s.a[i,j]*x[j]; } } } if( (double)(s.tau)>(double)(0) ) { for(i=0; i<=n-1; i++) { result = result+0.5*math.sqr(x[i])*s.tau*s.d[i]; } } return result; }
/************************************************************************* This subroutine evaluates (0.5*alpha*A+tau*D)*x Y is automatically resized if needed -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmadx(convexquadraticmodel s, double[] x, ref double[] y) { int n = 0; int i = 0; double v = 0; int i_ = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMEval: X is not finite vector"); apserv.rvectorsetlengthatleast(ref y, n); // // main quadratic term // for(i=0; i<=n-1; i++) { y[i] = 0; } if( (double)(s.alpha)>(double)(0) ) { for(i=0; i<=n-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += s.a[i,i_]*x[i_]; } y[i] = y[i]+s.alpha*v; } } if( (double)(s.tau)>(double)(0) ) { for(i=0; i<=n-1; i++) { y[i] = y[i]+x[i]*s.tau*s.d[i]; } } }
/************************************************************************* This subroutine evaluates model at X. Active constraints are ignored. It returns: R - model value Noise- estimate of the numerical noise in data -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmevalx(convexquadraticmodel s, double[] x, ref double r, ref double noise) { int n = 0; int i = 0; int j = 0; double v = 0; double v2 = 0; double mxq = 0; double eps = 0; r = 0; noise = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMEval: X is not finite vector"); r = 0.0; noise = 0.0; eps = 2*math.machineepsilon; mxq = 0.0; // // Main quadratic term. // // Noise from the main quadratic term is equal to the // maximum summand in the term. // if( (double)(s.alpha)>(double)(0) ) { for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { v = s.alpha*0.5*x[i]*s.a[i,j]*x[j]; r = r+v; noise = Math.Max(noise, eps*Math.Abs(v)); } } } if( (double)(s.tau)>(double)(0) ) { for(i=0; i<=n-1; i++) { v = 0.5*math.sqr(x[i])*s.tau*s.d[i]; r = r+v; noise = Math.Max(noise, eps*Math.Abs(v)); } } // // secondary quadratic term // // Noise from the secondary quadratic term is estimated as follows: // * noise in qi*x-r[i] is estimated as // Eps*MXQ = Eps*max(|r[i]|, |q[i,j]*x[j]|) // * noise in (qi*x-r[i])^2 is estimated as // NOISE = (|qi*x-r[i]|+Eps*MXQ)^2-(|qi*x-r[i]|)^2 // = Eps*MXQ*(2*|qi*x-r[i]|+Eps*MXQ) // if( (double)(s.theta)>(double)(0) ) { for(i=0; i<=s.k-1; i++) { v = 0.0; mxq = Math.Abs(s.r[i]); for(j=0; j<=n-1; j++) { v2 = s.q[i,j]*x[j]; v = v+v2; mxq = Math.Max(mxq, Math.Abs(v2)); } r = r+0.5*s.theta*math.sqr(v-s.r[i]); noise = Math.Max(noise, eps*mxq*(2*Math.Abs(v-s.r[i])+eps*mxq)); } } // // linear term // for(i=0; i<=s.n-1; i++) { r = r+x[i]*s.b[i]; noise = Math.Max(noise, eps*Math.Abs(x[i]*s.b[i])); } // // Final update of the noise // noise = n*noise; }
/************************************************************************* This subroutine evaluates gradient of the model; active constraints are ignored. INPUT PARAMETERS: S - convex model X - point, array[N] G - possibly preallocated buffer; resized, if too small -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmgradunconstrained(convexquadraticmodel s, double[] x, ref double[] g) { int n = 0; int i = 0; int j = 0; double v = 0; int i_ = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMEvalGradUnconstrained: X is not finite vector"); apserv.rvectorsetlengthatleast(ref g, n); for(i=0; i<=n-1; i++) { g[i] = 0; } // // main quadratic term // if( (double)(s.alpha)>(double)(0) ) { for(i=0; i<=n-1; i++) { v = 0.0; for(j=0; j<=n-1; j++) { v = v+s.alpha*s.a[i,j]*x[j]; } g[i] = g[i]+v; } } if( (double)(s.tau)>(double)(0) ) { for(i=0; i<=n-1; i++) { g[i] = g[i]+x[i]*s.tau*s.d[i]; } } // // secondary quadratic term // if( (double)(s.theta)>(double)(0) ) { for(i=0; i<=s.k-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += s.q[i,i_]*x[i_]; } v = s.theta*(v-s.r[i]); for(i_=0; i_<=n-1;i_++) { g[i_] = g[i_] + v*s.q[i,i_]; } } } // // linear term // for(i=0; i<=n-1; i++) { g[i] = g[i]+s.b[i]; } }
/************************************************************************* Internal function, rebuilds "effective" model subject to constraints. Returns False on failure (non-SPD main quadratic term) -- ALGLIB -- Copyright 10.05.2011 by Bochkanov Sergey *************************************************************************/ private static bool cqmrebuild(convexquadraticmodel s) { bool result = new bool(); int n = 0; int nfree = 0; int k = 0; int i = 0; int j = 0; int ridx0 = 0; int ridx1 = 0; int cidx0 = 0; int cidx1 = 0; double v = 0; int i_ = 0; if( (double)(s.alpha)==(double)(0) && (double)(s.tau)==(double)(0) ) { // // Non-SPD model, quick exit // result = false; return result; } result = true; n = s.n; k = s.k; // // Determine number of free variables. // Fill TXC - array whose last N-NFree elements store constraints. // if( s.isactivesetchanged ) { s.nfree = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { s.nfree = s.nfree+1; } } j = s.nfree; for(i=0; i<=n-1; i++) { if( s.activeset[i] ) { s.txc[j] = s.xc[i]; j = j+1; } } } nfree = s.nfree; // // Re-evaluate TQ2/TQ1/TQ0, if needed // if( s.isactivesetchanged || s.ismaintermchanged ) { // // Handle cases Alpha>0 and Alpha=0 separately: // * in the first case we have dense matrix // * in the second one we have diagonal matrix, which can be // handled more efficiently // if( (double)(s.alpha)>(double)(0) ) { // // Alpha>0, dense QP // // Split variables into two groups - free (F) and constrained (C). Reorder // variables in such way that free vars come first, constrained are last: // x = [xf, xc]. // // Main quadratic term x'*(alpha*A+tau*D)*x now splits into quadratic part, // linear part and constant part: // ( alpha*Aff+tau*Df alpha*Afc ) ( xf ) // 0.5*( xf' xc' )*( )*( ) = // ( alpha*Acf alpha*Acc+tau*Dc ) ( xc ) // // = 0.5*xf'*(alpha*Aff+tau*Df)*xf + (alpha*Afc*xc)'*xf + 0.5*xc'(alpha*Acc+tau*Dc)*xc // // We store these parts into temporary variables: // * alpha*Aff+tau*Df, alpha*Afc, alpha*Acc+tau*Dc are stored into upper // triangle of TQ2 // * alpha*Afc*xc is stored into TQ1 // * 0.5*xc'(alpha*Acc+tau*Dc)*xc is stored into TQ0 // // Below comes first part of the work - generation of TQ2: // * we pass through rows of A and copy I-th row into upper block (Aff/Afc) or // lower one (Acf/Acc) of TQ2, depending on presence of X[i] in the active set. // RIdx0 variable contains current position for insertion into upper block, // RIdx1 contains current position for insertion into lower one. // * within each row, we copy J-th element into left half (Aff/Acf) or right // one (Afc/Acc), depending on presence of X[j] in the active set. CIdx0 // contains current position for insertion into left block, CIdx1 contains // position for insertion into right one. // * during copying, we multiply elements by alpha and add diagonal matrix D. // ridx0 = 0; ridx1 = s.nfree; for(i=0; i<=n-1; i++) { cidx0 = 0; cidx1 = s.nfree; for(j=0; j<=n-1; j++) { if( !s.activeset[i] && !s.activeset[j] ) { // // Element belongs to Aff // v = s.alpha*s.a[i,j]; if( i==j && (double)(s.tau)>(double)(0) ) { v = v+s.tau*s.d[i]; } s.tq2dense[ridx0,cidx0] = v; } if( !s.activeset[i] && s.activeset[j] ) { // // Element belongs to Afc // s.tq2dense[ridx0,cidx1] = s.alpha*s.a[i,j]; } if( s.activeset[i] && !s.activeset[j] ) { // // Element belongs to Acf // s.tq2dense[ridx1,cidx0] = s.alpha*s.a[i,j]; } if( s.activeset[i] && s.activeset[j] ) { // // Element belongs to Acc // v = s.alpha*s.a[i,j]; if( i==j && (double)(s.tau)>(double)(0) ) { v = v+s.tau*s.d[i]; } s.tq2dense[ridx1,cidx1] = v; } if( s.activeset[j] ) { cidx1 = cidx1+1; } else { cidx0 = cidx0+1; } } if( s.activeset[i] ) { ridx1 = ridx1+1; } else { ridx0 = ridx0+1; } } // // Now we have TQ2, and we can evaluate TQ1. // In the special case when we have Alpha=0, NFree=0 or NFree=N, // TQ1 is filled by zeros. // for(i=0; i<=n-1; i++) { s.tq1[i] = 0.0; } if( s.nfree>0 && s.nfree<n ) { ablas.rmatrixmv(s.nfree, n-s.nfree, s.tq2dense, 0, s.nfree, 0, s.txc, s.nfree, ref s.tq1, 0); } // // And finally, we evaluate TQ0. // v = 0.0; for(i=s.nfree; i<=n-1; i++) { for(j=s.nfree; j<=n-1; j++) { v = v+0.5*s.txc[i]*s.tq2dense[i,j]*s.txc[j]; } } s.tq0 = v; } else { // // Alpha=0, diagonal QP // // Split variables into two groups - free (F) and constrained (C). Reorder // variables in such way that free vars come first, constrained are last: // x = [xf, xc]. // // Main quadratic term x'*(tau*D)*x now splits into quadratic and constant // parts: // ( tau*Df ) ( xf ) // 0.5*( xf' xc' )*( )*( ) = // ( tau*Dc ) ( xc ) // // = 0.5*xf'*(tau*Df)*xf + 0.5*xc'(tau*Dc)*xc // // We store these parts into temporary variables: // * tau*Df is stored in TQ2Diag // * 0.5*xc'(tau*Dc)*xc is stored into TQ0 // s.tq0 = 0.0; ridx0 = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { s.tq2diag[ridx0] = s.tau*s.d[i]; ridx0 = ridx0+1; } else { s.tq0 = s.tq0+0.5*s.tau*s.d[i]*math.sqr(s.xc[i]); } } for(i=0; i<=n-1; i++) { s.tq1[i] = 0.0; } } } // // Re-evaluate TK2/TK1/TK0, if needed // if( s.isactivesetchanged || s.issecondarytermchanged ) { // // Split variables into two groups - free (F) and constrained (C). Reorder // variables in such way that free vars come first, constrained are last: // x = [xf, xc]. // // Secondary term theta*(Q*x-r)'*(Q*x-r) now splits into quadratic part, // linear part and constant part: // ( ( xf ) )' ( ( xf ) ) // 0.5*theta*( (Qf Qc)'*( ) - r ) * ( (Qf Qc)'*( ) - r ) = // ( ( xc ) ) ( ( xc ) ) // // = 0.5*theta*xf'*(Qf'*Qf)*xf + theta*((Qc*xc-r)'*Qf)*xf + // + theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) // // We store these parts into temporary variables: // * sqrt(theta)*Qf is stored into TK2 // * theta*((Qc*xc-r)'*Qf) is stored into TK1 // * theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) is stored into TK0 // // We use several other temporaries to store intermediate results: // * Tmp0 - to store Qc*xc-r // * Tmp1 - to store Qc*xc // // Generation of TK2/TK1/TK0 is performed as follows: // * we fill TK2/TK1/TK0 (to handle K=0 or Theta=0) // * other steps are performed only for K>0 and Theta>0 // * we pass through columns of Q and copy I-th column into left block (Qf) or // right one (Qc) of TK2, depending on presence of X[i] in the active set. // CIdx0 variable contains current position for insertion into upper block, // CIdx1 contains current position for insertion into lower one. // * we calculate Qc*xc-r and store it into Tmp0 // * we calculate TK0 and TK1 // * we multiply leading part of TK2 which stores Qf by sqrt(theta) // it is important to perform this step AFTER calculation of TK0 and TK1, // because we need original (non-modified) Qf to calculate TK0 and TK1. // for(j=0; j<=n-1; j++) { for(i=0; i<=k-1; i++) { s.tk2[i,j] = 0.0; } s.tk1[j] = 0.0; } s.tk0 = 0.0; if( s.k>0 && (double)(s.theta)>(double)(0) ) { // // Split Q into Qf and Qc // Calculate Qc*xc-r, store in Tmp0 // apserv.rvectorsetlengthatleast(ref s.tmp0, k); apserv.rvectorsetlengthatleast(ref s.tmp1, k); cidx0 = 0; cidx1 = nfree; for(i=0; i<=k-1; i++) { s.tmp1[i] = 0.0; } for(j=0; j<=n-1; j++) { if( s.activeset[j] ) { for(i=0; i<=k-1; i++) { s.tk2[i,cidx1] = s.q[i,j]; s.tmp1[i] = s.tmp1[i]+s.q[i,j]*s.txc[cidx1]; } cidx1 = cidx1+1; } else { for(i=0; i<=k-1; i++) { s.tk2[i,cidx0] = s.q[i,j]; } cidx0 = cidx0+1; } } for(i=0; i<=k-1; i++) { s.tmp0[i] = s.tmp1[i]-s.r[i]; } // // Calculate TK0 // v = 0.0; for(i=0; i<=k-1; i++) { v = v+s.theta*(0.5*math.sqr(s.tmp1[i])-s.r[i]*s.tmp0[i]-0.5*math.sqr(s.r[i])); } s.tk0 = v; // // Calculate TK1 // if( nfree>0 ) { for(i=0; i<=k-1; i++) { v = s.theta*s.tmp0[i]; for(i_=0; i_<=nfree-1;i_++) { s.tk1[i_] = s.tk1[i_] + v*s.tk2[i,i_]; } } } // // Calculate TK2 // if( nfree>0 ) { v = Math.Sqrt(s.theta); for(i=0; i<=k-1; i++) { for(i_=0; i_<=nfree-1;i_++) { s.tk2[i,i_] = v*s.tk2[i,i_]; } } } } } // // Re-evaluate TB // if( s.isactivesetchanged || s.islineartermchanged ) { ridx0 = 0; ridx1 = nfree; for(i=0; i<=n-1; i++) { if( s.activeset[i] ) { s.tb[ridx1] = s.b[i]; ridx1 = ridx1+1; } else { s.tb[ridx0] = s.b[i]; ridx0 = ridx0+1; } } } // // Compose ECA: either dense ECA or diagonal ECA // if( (s.isactivesetchanged || s.ismaintermchanged) && nfree>0 ) { if( (double)(s.alpha)>(double)(0) ) { // // Dense ECA // s.ecakind = 0; for(i=0; i<=nfree-1; i++) { for(j=i; j<=nfree-1; j++) { s.ecadense[i,j] = s.tq2dense[i,j]; } } if( !trfac.spdmatrixcholeskyrec(ref s.ecadense, 0, nfree, true, ref s.tmp0) ) { result = false; return result; } } else { // // Diagonal ECA // s.ecakind = 1; for(i=0; i<=nfree-1; i++) { if( (double)(s.tq2diag[i])<(double)(0) ) { result = false; return result; } s.ecadiag[i] = Math.Sqrt(s.tq2diag[i]); } } } // // Compose EQ // if( s.isactivesetchanged || s.issecondarytermchanged ) { for(i=0; i<=k-1; i++) { for(j=0; j<=nfree-1; j++) { s.eq[i,j] = s.tk2[i,j]; } } } // // Calculate ECCM // if( ((((s.isactivesetchanged || s.ismaintermchanged) || s.issecondarytermchanged) && s.k>0) && (double)(s.theta)>(double)(0)) && nfree>0 ) { // // Calculate ECCM - Cholesky factor of the "effective" capacitance // matrix CM = I + EQ*inv(EffectiveA)*EQ'. // // We calculate CM as follows: // CM = I + EQ*inv(EffectiveA)*EQ' // = I + EQ*ECA^(-1)*ECA^(-T)*EQ' // = I + (EQ*ECA^(-1))*(EQ*ECA^(-1))' // // Then we perform Cholesky decomposition of CM. // apserv.rmatrixsetlengthatleast(ref s.tmp2, k, n); ablas.rmatrixcopy(k, nfree, s.eq, 0, 0, ref s.tmp2, 0, 0); alglib.ap.assert(s.ecakind==0 || s.ecakind==1, "CQMRebuild: unexpected ECAKind"); if( s.ecakind==0 ) { ablas.rmatrixrighttrsm(k, nfree, s.ecadense, 0, 0, true, false, 0, s.tmp2, 0, 0); } if( s.ecakind==1 ) { for(i=0; i<=k-1; i++) { for(j=0; j<=nfree-1; j++) { s.tmp2[i,j] = s.tmp2[i,j]/s.ecadiag[j]; } } } for(i=0; i<=k-1; i++) { for(j=0; j<=k-1; j++) { s.eccm[i,j] = 0.0; } s.eccm[i,i] = 1.0; } ablas.rmatrixsyrk(k, nfree, 1.0, s.tmp2, 0, 0, 0, 1.0, s.eccm, 0, 0, true); if( !trfac.spdmatrixcholeskyrec(ref s.eccm, 0, k, true, ref s.tmp0) ) { result = false; return result; } } // // Compose EB and EC // // NOTE: because these quantities are cheap to compute, we do not // use caching here. // for(i=0; i<=nfree-1; i++) { s.eb[i] = s.tq1[i]+s.tk1[i]+s.tb[i]; } s.ec = s.tq0+s.tk0; for(i=nfree; i<=n-1; i++) { s.ec = s.ec+s.tb[i]*s.txc[i]; } // // Change cache status - everything is cached // s.ismaintermchanged = false; s.issecondarytermchanged = false; s.islineartermchanged = false; s.isactivesetchanged = false; return result; }
/************************************************************************* This subroutine is used to initialize CQM. By default, empty NxN model is generated, with Alpha=Lambda=Theta=0.0 and zero b. Previously allocated buffer variables are reused as much as possible. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqminit(int n, convexquadraticmodel s) { int i = 0; s.n = n; s.k = 0; s.nfree = n; s.ecakind = -1; s.alpha = 0.0; s.tau = 0.0; s.theta = 0.0; s.ismaintermchanged = true; s.issecondarytermchanged = true; s.islineartermchanged = true; s.isactivesetchanged = true; apserv.bvectorsetlengthatleast(ref s.activeset, n); apserv.rvectorsetlengthatleast(ref s.xc, n); apserv.rvectorsetlengthatleast(ref s.eb, n); apserv.rvectorsetlengthatleast(ref s.tq1, n); apserv.rvectorsetlengthatleast(ref s.txc, n); apserv.rvectorsetlengthatleast(ref s.tb, n); apserv.rvectorsetlengthatleast(ref s.b, s.n); apserv.rvectorsetlengthatleast(ref s.tk1, s.n); for(i=0; i<=n-1; i++) { s.activeset[i] = false; s.xc[i] = 0.0; s.b[i] = 0.0; } }
/************************************************************************* This subroutine changes linear term of the model -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmsetb(convexquadraticmodel s, double[] b) { int i = 0; alglib.ap.assert(apserv.isfinitevector(b, s.n), "CQMSetB: B is not finite vector"); apserv.rvectorsetlengthatleast(ref s.b, s.n); for(i=0; i<=s.n-1; i++) { s.b[i] = b[i]; } s.islineartermchanged = true; }
/************************************************************************* This subroutine calls CQMRebuild() and evaluates model at X subject to active constraints. It is intended for debug purposes only, because it evaluates model by means of temporaries, which were calculated by CQMRebuild(). The only purpose of this function is to check correctness of CQMRebuild() by comparing results of this function with ones obtained by CQMEval(), which is used as reference point. The idea is that significant deviation in results of these two functions is evidence of some error in the CQMRebuild(). NOTE: suffix T denotes that temporaries marked by T-prefix are used. There is one more variant of this function, which uses "effective" model built by CQMRebuild(). NOTE2: in case CQMRebuild() fails (due to model non-convexity), this function returns NAN. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static double cqmdebugconstrainedevalt(convexquadraticmodel s, double[] x) { double result = 0; int n = 0; int nfree = 0; int i = 0; int j = 0; double v = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMDebugConstrainedEvalT: X is not finite vector"); if( !cqmrebuild(s) ) { result = Double.NaN; return result; } result = 0.0; nfree = s.nfree; // // Reorder variables // j = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { alglib.ap.assert(j<nfree, "CQMDebugConstrainedEvalT: internal error"); s.txc[j] = x[i]; j = j+1; } } // // TQ2, TQ1, TQ0 // // if( (double)(s.alpha)>(double)(0) ) { // // Dense TQ2 // for(i=0; i<=nfree-1; i++) { for(j=0; j<=nfree-1; j++) { result = result+0.5*s.txc[i]*s.tq2dense[i,j]*s.txc[j]; } } } else { // // Diagonal TQ2 // for(i=0; i<=nfree-1; i++) { result = result+0.5*s.tq2diag[i]*math.sqr(s.txc[i]); } } for(i=0; i<=nfree-1; i++) { result = result+s.tq1[i]*s.txc[i]; } result = result+s.tq0; // // TK2, TK1, TK0 // if( s.k>0 && (double)(s.theta)>(double)(0) ) { for(i=0; i<=s.k-1; i++) { v = 0; for(j=0; j<=nfree-1; j++) { v = v+s.tk2[i,j]*s.txc[j]; } result = result+0.5*math.sqr(v); } for(i=0; i<=nfree-1; i++) { result = result+s.tk1[i]*s.txc[i]; } result = result+s.tk0; } // // TB (Bf and Bc parts) // for(i=0; i<=n-1; i++) { result = result+s.tb[i]*s.txc[i]; } return result; }
/************************************************************************* This subroutine changes diagonal quadratic term of the model. INPUT PARAMETERS: S - model D - array[N], semidefinite diagonal matrix Tau - multiplier; when Tau=0, D is not referenced at all -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmsetd(convexquadraticmodel s, double[] d, double tau) { int i = 0; alglib.ap.assert(math.isfinite(tau) && (double)(tau)>=(double)(0), "CQMSetD: Tau<0 or is not finite number"); alglib.ap.assert((double)(tau)==(double)(0) || apserv.isfinitevector(d, s.n), "CQMSetD: D is not finite Nx1 vector"); s.tau = tau; if( (double)(tau)>(double)(0) ) { apserv.rvectorsetlengthatleast(ref s.d, s.n); apserv.rvectorsetlengthatleast(ref s.ecadiag, s.n); apserv.rvectorsetlengthatleast(ref s.tq2diag, s.n); for(i=0; i<=s.n-1; i++) { alglib.ap.assert((double)(d[i])>=(double)(0), "CQMSetD: D[i]<0"); s.d[i] = d[i]; } } s.ismaintermchanged = true; }
/************************************************************************* This subroutine drops main quadratic term A from the model. It is same as call to CQMSetA() with zero A, but gives better performance because algorithm knows that matrix is zero and can optimize subsequent calculations. INPUT PARAMETERS: S - model -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmdropa(convexquadraticmodel s) { s.alpha = 0.0; s.ismaintermchanged = true; }
/************************************************************************* This subroutine rewrites diagonal of the main quadratic term of the model (dense A) by vector Z/Alpha (current value of the Alpha coefficient is used). IMPORTANT: in case model has no dense quadratic term, this function allocates N*N dense matrix of zeros, and fills its diagonal by non-zero values. INPUT PARAMETERS: S - model Z - new diagonal, array[N] -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmrewritedensediagonal(convexquadraticmodel s, double[] z) { int n = 0; int i = 0; int j = 0; n = s.n; if( (double)(s.alpha)==(double)(0) ) { apserv.rmatrixsetlengthatleast(ref s.a, s.n, s.n); apserv.rmatrixsetlengthatleast(ref s.ecadense, s.n, s.n); apserv.rmatrixsetlengthatleast(ref s.tq2dense, s.n, s.n); for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { s.a[i,j] = 0.0; } } s.alpha = 1.0; } for(i=0; i<=s.n-1; i++) { s.a[i,i] = z[i]/s.alpha; } s.ismaintermchanged = true; }
/************************************************************************* This subroutine changes main quadratic term of the model. INPUT PARAMETERS: S - model A - possibly preallocated buffer OUTPUT PARAMETERS: A - NxN matrix, full matrix is returned. Zero matrix is returned if model is empty. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmgeta(convexquadraticmodel s, ref double[,] a) { int i = 0; int j = 0; double v = 0; int n = 0; n = s.n; apserv.rmatrixsetlengthatleast(ref a, n, n); if( (double)(s.alpha)>(double)(0) ) { v = s.alpha; for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { a[i,j] = v*s.a[i,j]; } } } else { for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { a[i,j] = 0.0; } } } }
/************************************************************************* This subroutine changes main quadratic term of the model. INPUT PARAMETERS: S - model A - NxN matrix, only upper or lower triangle is referenced IsUpper - True, when matrix is stored in upper triangle Alpha - multiplier; when Alpha=0, A is not referenced at all -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmseta(convexquadraticmodel s, double[,] a, bool isupper, double alpha) { int i = 0; int j = 0; double v = 0; alglib.ap.assert(math.isfinite(alpha) && (double)(alpha)>=(double)(0), "CQMSetA: Alpha<0 or is not finite number"); alglib.ap.assert((double)(alpha)==(double)(0) || apserv.isfinitertrmatrix(a, s.n, isupper), "CQMSetA: A is not finite NxN matrix"); s.alpha = alpha; if( (double)(alpha)>(double)(0) ) { apserv.rmatrixsetlengthatleast(ref s.a, s.n, s.n); apserv.rmatrixsetlengthatleast(ref s.ecadense, s.n, s.n); apserv.rmatrixsetlengthatleast(ref s.tq2dense, s.n, s.n); for(i=0; i<=s.n-1; i++) { for(j=i; j<=s.n-1; j++) { if( isupper ) { v = a[i,j]; } else { v = a[j,i]; } s.a[i,j] = v; s.a[j,i] = v; } } } s.ismaintermchanged = true; }
/************************************************************************* This subroutine finds optimum of the model. It returns False on failure (indefinite/semidefinite matrix). Optimum is found subject to active constraints. INPUT PARAMETERS S - model X - possibly preallocated buffer; automatically resized, if too small enough. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static bool cqmconstrainedoptimum(convexquadraticmodel s, ref double[] x) { bool result = new bool(); int n = 0; int nfree = 0; int k = 0; int i = 0; double v = 0; int cidx0 = 0; int itidx = 0; int i_ = 0; // // Rebuild internal structures // if( !cqmrebuild(s) ) { result = false; return result; } n = s.n; k = s.k; nfree = s.nfree; result = true; // // Calculate initial point for the iterative refinement: // * free components are set to zero // * constrained components are set to their constrained values // apserv.rvectorsetlengthatleast(ref x, n); for(i=0; i<=n-1; i++) { if( s.activeset[i] ) { x[i] = s.xc[i]; } else { x[i] = 0; } } // // Iterative refinement. // // In an ideal world without numerical errors it would be enough // to make just one Newton step from initial point: // x_new = -H^(-1)*grad(x=0) // However, roundoff errors can significantly deteriorate quality // of the solution. So we have to recalculate gradient and to // perform Newton steps several times. // // Below we perform fixed number of Newton iterations. // for(itidx=0; itidx<=newtonrefinementits-1; itidx++) { // // Calculate gradient at the current point. // Move free components of the gradient in the beginning. // cqmgradunconstrained(s, x, ref s.tmpg); cidx0 = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { s.tmpg[cidx0] = s.tmpg[i]; cidx0 = cidx0+1; } } // // Free components of the extrema are calculated in the first NFree elements of TXC. // // First, we have to calculate original Newton step, without rank-K perturbations // for(i_=0; i_<=nfree-1;i_++) { s.txc[i_] = -s.tmpg[i_]; } cqmsolveea(s, ref s.txc, ref s.tmp0); // // Then, we account for rank-K correction. // Woodbury matrix identity is used. // if( s.k>0 && (double)(s.theta)>(double)(0) ) { apserv.rvectorsetlengthatleast(ref s.tmp0, Math.Max(nfree, k)); apserv.rvectorsetlengthatleast(ref s.tmp1, Math.Max(nfree, k)); for(i_=0; i_<=nfree-1;i_++) { s.tmp1[i_] = -s.tmpg[i_]; } cqmsolveea(s, ref s.tmp1, ref s.tmp0); for(i=0; i<=k-1; i++) { v = 0.0; for(i_=0; i_<=nfree-1;i_++) { v += s.eq[i,i_]*s.tmp1[i_]; } s.tmp0[i] = v; } fbls.fblscholeskysolve(s.eccm, 1.0, k, true, s.tmp0, ref s.tmp1); for(i=0; i<=nfree-1; i++) { s.tmp1[i] = 0.0; } for(i=0; i<=k-1; i++) { v = s.tmp0[i]; for(i_=0; i_<=nfree-1;i_++) { s.tmp1[i_] = s.tmp1[i_] + v*s.eq[i,i_]; } } cqmsolveea(s, ref s.tmp1, ref s.tmp0); for(i_=0; i_<=nfree-1;i_++) { s.txc[i_] = s.txc[i_] - s.tmp1[i_]; } } // // Unpack components from TXC into X. We pass through all // free components of X and add our step. // cidx0 = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { x[i] = x[i]+s.txc[cidx0]; cidx0 = cidx0+1; } } } return result; }
/************************************************************************* This subroutine changes linear term of the model -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmsetq(convexquadraticmodel s, double[,] q, double[] r, int k, double theta) { int i = 0; int j = 0; alglib.ap.assert(k>=0, "CQMSetQ: K<0"); alglib.ap.assert((k==0 || (double)(theta)==(double)(0)) || apserv.apservisfinitematrix(q, k, s.n), "CQMSetQ: Q is not finite matrix"); alglib.ap.assert((k==0 || (double)(theta)==(double)(0)) || apserv.isfinitevector(r, k), "CQMSetQ: R is not finite vector"); alglib.ap.assert(math.isfinite(theta) && (double)(theta)>=(double)(0), "CQMSetQ: Theta<0 or is not finite number"); // // degenerate case: K=0 or Theta=0 // if( k==0 || (double)(theta)==(double)(0) ) { s.k = 0; s.theta = 0; s.issecondarytermchanged = true; return; } // // General case: both Theta>0 and K>0 // s.k = k; s.theta = theta; apserv.rmatrixsetlengthatleast(ref s.q, s.k, s.n); apserv.rvectorsetlengthatleast(ref s.r, s.k); apserv.rmatrixsetlengthatleast(ref s.eq, s.k, s.n); apserv.rmatrixsetlengthatleast(ref s.eccm, s.k, s.k); apserv.rmatrixsetlengthatleast(ref s.tk2, s.k, s.n); for(i=0; i<=s.k-1; i++) { for(j=0; j<=s.n-1; j++) { s.q[i,j] = q[i,j]; } s.r[i] = r[i]; } s.issecondarytermchanged = true; }
/************************************************************************* This function scales vector by multiplying it by inverse of the diagonal of the Hessian matrix. It should be used to accelerate steepest descent phase of the QP solver. Although it is called "scale-grad", it can be called for any vector, whether it is gradient, anti-gradient, or just some vector. This function does NOT takes into account current set of constraints, it just performs matrix-vector multiplication without taking into account constraints. INPUT PARAMETERS: S - model X - vector to scale OUTPUT PARAMETERS: X - scaled vector NOTE: when called for non-SPD matrices, it silently skips components of X which correspond to zero or negative diagonal elements. NOTE: this function uses diagonals of A and D; it ignores Q - rank-K term of the quadratic model. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmscalevector(convexquadraticmodel s, ref double[] x) { int n = 0; int i = 0; double v = 0; n = s.n; for(i=0; i<=n-1; i++) { v = 0.0; if( (double)(s.alpha)>(double)(0) ) { v = v+s.a[i,i]; } if( (double)(s.tau)>(double)(0) ) { v = v+s.d[i]; } if( (double)(v)>(double)(0) ) { x[i] = x[i]/v; } } }
/************************************************************************* This subroutine changes active set INPUT PARAMETERS S - model X - array[N], constraint values ActiveSet- array[N], active set. If ActiveSet[I]=True, then I-th variables is constrained to X[I]. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static void cqmsetactiveset(convexquadraticmodel s, double[] x, bool[] activeset) { int i = 0; alglib.ap.assert(alglib.ap.len(x)>=s.n, "CQMSetActiveSet: Length(X)<N"); alglib.ap.assert(alglib.ap.len(activeset)>=s.n, "CQMSetActiveSet: Length(ActiveSet)<N"); for(i=0; i<=s.n-1; i++) { s.isactivesetchanged = s.isactivesetchanged || (s.activeset[i] && !activeset[i]); s.isactivesetchanged = s.isactivesetchanged || (activeset[i] && !s.activeset[i]); s.activeset[i] = activeset[i]; if( activeset[i] ) { alglib.ap.assert(math.isfinite(x[i]), "CQMSetActiveSet: X[] contains infinite constraints"); s.isactivesetchanged = s.isactivesetchanged || (double)(s.xc[i])!=(double)(x[i]); s.xc[i] = x[i]; } } }
/************************************************************************* This subroutine calls CQMRebuild() and evaluates model at X subject to active constraints. It is intended for debug purposes only, because it evaluates model by means of "effective" matrices built by CQMRebuild(). The only purpose of this function is to check correctness of CQMRebuild() by comparing results of this function with ones obtained by CQMEval(), which is used as reference point. The idea is that significant deviation in results of these two functions is evidence of some error in the CQMRebuild(). NOTE: suffix E denotes that effective matrices. There is one more variant of this function, which uses temporary matrices built by CQMRebuild(). NOTE2: in case CQMRebuild() fails (due to model non-convexity), this function returns NAN. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static double cqmdebugconstrainedevale(convexquadraticmodel s, double[] x) { double result = 0; int n = 0; int nfree = 0; int i = 0; int j = 0; double v = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMDebugConstrainedEvalE: X is not finite vector"); if( !cqmrebuild(s) ) { result = Double.NaN; return result; } result = 0.0; nfree = s.nfree; // // Reorder variables // j = 0; for(i=0; i<=n-1; i++) { if( !s.activeset[i] ) { alglib.ap.assert(j<nfree, "CQMDebugConstrainedEvalE: internal error"); s.txc[j] = x[i]; j = j+1; } } // // ECA // alglib.ap.assert((s.ecakind==0 || s.ecakind==1) || (s.ecakind==-1 && nfree==0), "CQMDebugConstrainedEvalE: unexpected ECAKind"); if( s.ecakind==0 ) { // // Dense ECA // for(i=0; i<=nfree-1; i++) { v = 0.0; for(j=i; j<=nfree-1; j++) { v = v+s.ecadense[i,j]*s.txc[j]; } result = result+0.5*math.sqr(v); } } if( s.ecakind==1 ) { // // Diagonal ECA // for(i=0; i<=nfree-1; i++) { result = result+0.5*math.sqr(s.ecadiag[i]*s.txc[i]); } } // // EQ // for(i=0; i<=s.k-1; i++) { v = 0.0; for(j=0; j<=nfree-1; j++) { v = v+s.eq[i,j]*s.txc[j]; } result = result+0.5*math.sqr(v); } // // EB // for(i=0; i<=nfree-1; i++) { result = result+s.eb[i]*s.txc[i]; } // // EC // result = result+s.ec; return result; }
/************************************************************************* This subroutine evaluates model at X. Active constraints are ignored. -- ALGLIB -- Copyright 12.06.2012 by Bochkanov Sergey *************************************************************************/ public static double cqmeval(convexquadraticmodel s, double[] x) { double result = 0; int n = 0; int i = 0; int j = 0; double v = 0; int i_ = 0; n = s.n; alglib.ap.assert(apserv.isfinitevector(x, n), "CQMEval: X is not finite vector"); result = 0.0; // // main quadratic term // if( (double)(s.alpha)>(double)(0) ) { for(i=0; i<=n-1; i++) { for(j=0; j<=n-1; j++) { result = result+s.alpha*0.5*x[i]*s.a[i,j]*x[j]; } } } if( (double)(s.tau)>(double)(0) ) { for(i=0; i<=n-1; i++) { result = result+0.5*math.sqr(x[i])*s.tau*s.d[i]; } } // // secondary quadratic term // if( (double)(s.theta)>(double)(0) ) { for(i=0; i<=s.k-1; i++) { v = 0.0; for(i_=0; i_<=n-1;i_++) { v += s.q[i,i_]*x[i_]; } result = result+0.5*s.theta*math.sqr(v-s.r[i]); } } // // linear term // for(i=0; i<=s.n-1; i++) { result = result+x[i]*s.b[i]; } return result; }
/************************************************************************* Internal function, solves system Effective_A*x = b. It should be called after successful completion of CQMRebuild(). INPUT PARAMETERS: S - quadratic model, after call to CQMRebuild() X - right part B, array[S.NFree] Tmp - temporary array, automatically reallocated if needed OUTPUT PARAMETERS: X - solution, array[S.NFree] NOTE: when called with zero S.NFree, returns silently NOTE: this function assumes that EA is non-degenerate -- ALGLIB -- Copyright 10.05.2011 by Bochkanov Sergey *************************************************************************/ private static void cqmsolveea(convexquadraticmodel s, ref double[] x, ref double[] tmp) { int i = 0; alglib.ap.assert((s.ecakind==0 || s.ecakind==1) || (s.ecakind==-1 && s.nfree==0), "CQMSolveEA: unexpected ECAKind"); if( s.ecakind==0 ) { // // Dense ECA, use FBLSCholeskySolve() dense solver. // fbls.fblscholeskysolve(s.ecadense, 1.0, s.nfree, true, x, ref tmp); } if( s.ecakind==1 ) { // // Diagonal ECA // for(i=0; i<=s.nfree-1; i++) { x[i] = x[i]/math.sqr(s.ecadiag[i]); } } }
public override alglib.apobject make_copy() { convexquadraticmodel _result = new convexquadraticmodel(); _result.n = n; _result.k = k; _result.alpha = alpha; _result.tau = tau; _result.theta = theta; _result.a = (double[,])a.Clone(); _result.q = (double[,])q.Clone(); _result.b = (double[])b.Clone(); _result.r = (double[])r.Clone(); _result.xc = (double[])xc.Clone(); _result.d = (double[])d.Clone(); _result.activeset = (bool[])activeset.Clone(); _result.tq2dense = (double[,])tq2dense.Clone(); _result.tk2 = (double[,])tk2.Clone(); _result.tq2diag = (double[])tq2diag.Clone(); _result.tq1 = (double[])tq1.Clone(); _result.tk1 = (double[])tk1.Clone(); _result.tq0 = tq0; _result.tk0 = tk0; _result.txc = (double[])txc.Clone(); _result.tb = (double[])tb.Clone(); _result.nfree = nfree; _result.ecakind = ecakind; _result.ecadense = (double[,])ecadense.Clone(); _result.eq = (double[,])eq.Clone(); _result.eccm = (double[,])eccm.Clone(); _result.ecadiag = (double[])ecadiag.Clone(); _result.eb = (double[])eb.Clone(); _result.ec = ec; _result.tmp0 = (double[])tmp0.Clone(); _result.tmp1 = (double[])tmp1.Clone(); _result.tmpg = (double[])tmpg.Clone(); _result.tmp2 = (double[,])tmp2.Clone(); _result.ismaintermchanged = ismaintermchanged; _result.issecondarytermchanged = issecondarytermchanged; _result.islineartermchanged = islineartermchanged; _result.isactivesetchanged = isactivesetchanged; return _result; }