/* * vim: ts=4 sw=4 et tw=0 wm=0 * * libcola - A library providing force-directed network layout using the * stress-majorization method subject to separation constraints. * * Copyright (C) 2006-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * See the file LICENSE.LGPL distributed with the library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ /********************************************************** * * Solve a quadratic function f(X) = X' A X + b X * subject to a set of separation constraints cs * **********************************************************/ #include #include #include #include #include #include #include #include "libcola/cluster.h" #include "libcola/gradient_projection.h" #include "libcola/straightener.h" #include "libcola/commondefs.h" //#define CHECK_CONVERGENCE_BY_COST 1 using namespace std; using namespace vpsc; namespace cola { GradientProjection::GradientProjection( const Dim k, std::valarray *denseQ, const double tol, const unsigned max_iterations, CompoundConstraints const *ccs, UnsatisfiableConstraintInfos *unsatisfiableConstraints, NonOverlapConstraintsMode nonOverlapConstraints, RootCluster* clusterHierarchy, vpsc::Rectangles* rs, const bool scaling, SolveWithMosek solveWithMosek) : k(k), denseSize(static_cast((floor(sqrt(static_cast(denseQ->size())))))), denseQ(denseQ), rs(rs), ccs(ccs), unsatisfiableConstraints(unsatisfiableConstraints), nonOverlapConstraints(nonOverlapConstraints), clusterHierarchy(clusterHierarchy), tolerance(tol), max_iterations(max_iterations), sparseQ(nullptr), solveWithMosek(solveWithMosek), scaling(scaling) { //printf("GP Instance: scaling=%d, mosek=%d\n",scaling,solveWithMosek); for(unsigned i=0;iscale=1./sqrt(fabs((*denseQ)[i*denseSize+i])); // XXX: Scale can sometimes be set to infinity here when // there are nodes not connected to any other node. // Thus we just set the scale for such a variable to 1. if (!std::isfinite(vars[i]->scale)) { vars[i]->scale = 1; } } // the following computes S'QS for Q=denseQ // and S is diagonal matrix of scale factors for(unsigned i=0;iscale *vars[j]->scale; } } this->denseQ = &scaledDenseQ; } //dumpSquareMatrix(*this->denseQ); //dumpSquareMatrix(scaledDenseQ); if(ccs) { for(CompoundConstraints::const_iterator c=ccs->begin(); c!=ccs->end();++c) { (*c)->generateVariables(k, vars); OrthogonalEdgeConstraint* e=dynamic_cast(*c); if(e) { orthogonalEdges.push_back(e); } } for(CompoundConstraints::const_iterator c=ccs->begin(); c!=ccs->end();++c) { (*c)->generateSeparationConstraints(k, vars, gcs, *rs); } } /* if(clusterHierarchy) { clusterHierarchy->createVars(k,*rs,vars); } */ numStaticVars=vars.size(); //solver=setupVPSC(); } static inline double dotProd(valarray const & a, valarray const & b) { double p = 0; for (unsigned i=0; i const &b, valarray const &x) const { // computes cost = 2 b x - x A x double cost = 2. * dotProd(b,x); valarray Ax(x.size()); for (unsigned i=0; i r(x.size()); sparseQ->rightMultiply(x,r); Ax+=r; } return cost - dotProd(x,Ax); } double GradientProjection::computeSteepestDescentVector( valarray const &b, valarray const &x, valarray &g) const { // find steepest descent direction // g = 2 ( b - A x ) // where: A = denseQ + sparseQ // g = 2 ( b - denseQ x) - 2 sparseQ x // // except the 2s don't matter because we compute // the optimal stepsize anyway COLA_ASSERT(x.size()==b.size() && b.size()==g.size()); g = b; for (unsigned i=0; i r(x.size()); sparseQ->rightMultiply(x,r); g-=r; } return computeStepSize(g,g); } // compute optimal step size along descent vector d relative to // a gradient related vector g // stepsize = ( g' d ) / ( d' A d ) double GradientProjection::computeStepSize( valarray const & g, valarray const & d) const { COLA_ASSERT(g.size()==d.size()); valarray Ad; if(sparseQ) { Ad.resize(g.size()); sparseQ->rightMultiply(d,Ad); } double const numerator = dotProd(g, d); double denominator = 0; for (unsigned i=0; i & result) { bool activeConstraints = false; switch(solveWithMosek) { case Off: activeConstraints = solver->satisfy(); for (unsigned i=0;ifinalPosition; } break; case Inner: #ifdef MOSEK_AVAILABLE float *ba=new float[vars.size()]; float *coords=new float[vars.size()]; for(unsigned i=0;idesiredPosition; } mosek_quad_solve_sep(menv,ba,coords); for(unsigned i=0;i const &linearCoefficients, valarray &x) { COLA_ASSERT(linearCoefficients.size()==x.size()); COLA_ASSERT(x.size()==denseSize); COLA_ASSERT(numStaticVars>=denseSize); COLA_ASSERT(sparseQ==nullptr || (sparseQ!=nullptr && (vars.size()==sparseQ->rowSize())) ); if(max_iterations==0) return 0; bool converged=false; solver = setupVPSC(); #ifdef MOSEK_AVAILABLE if(solveWithMosek==Outer) { float* ba=new float[vars.size()]; float* xa=new float[vars.size()]; for(unsigned i=0;i b(n); result.resize(n); // load desired positions into vars, note that we keep desired positions // already calculated for dummy vars for (unsigned i=0;iscale; result[i]/=vars[i]->scale; } if(!vars[i]->fixedDesiredPosition) vars[i]->desiredPosition=result[i]; } runSolver(result); valarray g(n); /* gradient */ valarray previous(n); /* stored positions */ valarray d(n); /* actual descent vector */ #ifdef CHECK_CONVERGENCE_BY_COST double previousCost = DBL_MAX; #endif unsigned counter=0; double stepSize; for (; counterweight; result[i]+=step; //printf(" after unconstrained step: x[%d]=%f\n",i,result[i]); stepSize+=step*step; COLA_ASSERT(!std::isnan(result[i])); COLA_ASSERT(std::isfinite(result[i])); if(!vars[i]->fixedDesiredPosition) vars[i]->desiredPosition=result[i]; } //project to constraint boundary bool constrainedOptimum = false; constrainedOptimum=runSolver(result); stepSize=0; for (unsigned i=0;i1! if(constrainedOptimum) { // The following step limits the step-size in the feasible // direction d = result - previous; const double beta = 0.5*computeStepSize(g, d); // beta > 1.0 takes us back outside the feasible region // beta < 0 clearly not useful and may happen due to numerical imp. //printf("beta=%f\n",beta); if(beta>0&&beta<0.99999) { stepSize=0; for (unsigned i=0; icost); if(fabs(previousCost - cost) < tolerance) { converged = true; } previousCost = cost; //} #else if(stepSizescale; } } destroyVPSC(solver); return counter; } // Setup an instance of the Variable Placement with Separation Constraints // for one iteration. // Generate transient local constraints --- such as non-overlap constraints // --- that are only relevant to one iteration, and merge these with the // global constraint list (including alignment constraints, // dir-edge constraints, containment constraints, etc). IncSolver* GradientProjection::setupVPSC() { if(nonOverlapConstraints!=None) { if(clusterHierarchy) { //printf("Setup up cluster constraints, dim=%d--------------\n",k); //clusterHierarchy->generateNonOverlapConstraints(k,nonOverlapConstraints,*rs,vars,lcs); } else { for(vector::iterator i=orthogonalEdges.begin();i!=orthogonalEdges.end();i++) { OrthogonalEdgeConstraint* e=*i; e->generateTopologyConstraints(k,*rs,vars,lcs); } if(k==HORIZONTAL) { // Make rectangles a little bit wider when processing horizontally so that any overlap // resolved horizontally is strictly non-overlapping when processing vertically Rectangle::setXBorder(0.0001); // use rs->size() rather than n because some of the variables may // be dummy vars with no corresponding rectangle generateXConstraints(*rs,vars,lcs,nonOverlapConstraints==Both?true:false); Rectangle::setXBorder(0); } else { generateYConstraints(*rs,vars,lcs); } } } cs=gcs; cs.insert(cs.end(),lcs.begin(),lcs.end()); switch(solveWithMosek) { case Off: break; #ifdef MOSEK_AVAILABLE case Inner: menv = mosek_init_sep_ls(vars.size(),cs); break; case Outer: unsigned n = vars.size(); float* lap = new float[n*(n+1)/2]; unsigned k=0; for(unsigned i=0;ibegin(); c!=ccs->end();++c) { (*c)->updatePosition(vpsc::XDIM); (*c)->updatePosition(vpsc::YDIM); } } if(unsatisfiableConstraints) { unsatisfiableConstraints->clear(); for(Constraints::iterator i=cs.begin();i!=cs.end();i++) { Constraint* c=*i; if(c->unsatisfiable) { UnsatisfiableConstraintInfo *ci = new UnsatisfiableConstraintInfo(c); unsatisfiableConstraints->push_back(ci); } } } if(clusterHierarchy) { clusterHierarchy->computeBoundary(*rs); } if(sparseQ) { for(unsigned i=numStaticVars;i::iterator i=lcs.begin();i!=lcs.end();i++) { delete *i; } lcs.clear(); delete vpsc; #ifdef MOSEK_AVAILABLE if(solveWithMosek!=Off) mosek_delete(menv); #endif } void GradientProjection::straighten( cola::SparseMatrix const * Q, vector const & cs, vector const & snodes) { COLA_ASSERT(Q->rowSize()==snodes.size()); COLA_ASSERT(vars.size()==numStaticVars); sparseQ = Q; for(unsigned i=numStaticVars;ipos[k],1); COLA_ASSERT(v->desiredPosition==snodes[i]->pos[k]); vars.push_back(v); } COLA_ASSERT(lcs.size()==0); for(vector::const_iterator i=cs.begin();i!=cs.end();i++) { (*i)->generateSeparationConstraints(k, vars, lcs, *rs); } } } // namespace cola