From fd733201b82f39655488a286c89142f321ef9dc9 Mon Sep 17 00:00:00 2001 From: Sylvain Chiron Date: Sat, 1 Jul 2017 13:36:41 +0200 Subject: Updated libs from the Adaptagrams project: libavoid, libcola and libvspc; changed the code to match the new API Signed-off-by: Sylvain Chiron --- src/libcola/gradient_projection.cpp | 614 +++++++++++++++++++++++++----------- 1 file changed, 430 insertions(+), 184 deletions(-) (limited to 'src/libcola/gradient_projection.cpp') diff --git a/src/libcola/gradient_projection.cpp b/src/libcola/gradient_projection.cpp index c079c3149..50b8d27d7 100644 --- a/src/libcola/gradient_projection.cpp +++ b/src/libcola/gradient_projection.cpp @@ -1,165 +1,372 @@ +/* + * 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 * - * Tim Dwyer, 2006 **********************************************************/ -#include -#include -#include -#include -#include -#include +#include +#include +#include + #include #include +#include #include -#include "gradient_projection.h" -#include -#include <2geom/math-utils.h> -#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; -//#define CONMAJ_LOGGING 1 +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(NULL), + 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 (!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); +} -static void dumpVPSCException(char const *str, IncSolver* solver) { - cerr<getConstraints(m); - for(unsigned i=0;i 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==NULL || + (sparseQ!=NULL && (vars.size()==sparseQ->rowSize())) ); - bool converged=false; + if(max_iterations==0) return 0; - IncSolver* solver=NULL; + bool converged=false; solver = setupVPSC(); - //cerr << "in gradient projection: n=" << n << endl; - for (i=0;idesiredPosition=place[i]; - } - try { - solver->satisfy(); - } catch (char const *str) { - dumpVPSCException(str,solver); - } - - for (i=0;iposition(); - } - for (DummyVars::iterator it=dummy_vars.begin();it!=dummy_vars.end();++it){ - (*it)->updatePosition(); - } - - for (counter=0; countercomputeDescentVector(); - } - // compute step size: alpha = ( g' g ) / ( 2 g' A g ) - // g terms for dummy vars cancel out so don't consider - double numerator = 0, denominator = 0, r; - for (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 (; counterdesiredPosition=place[i]; - } - for (DummyVars::iterator it=dummy_vars.begin();it!=dummy_vars.end();++it){ - (*it)->steepestDescent(alpha); + for (unsigned i=0; iweight; + result[i]+=step; + //printf(" after unconstrained step: x[%d]=%f\n",i,result[i]); + stepSize+=step*step; + COLA_ASSERT(!isNaN(result[i])); + COLA_ASSERT(isFinite(result[i])); + if(!vars[i]->fixedDesiredPosition) vars[i]->desiredPosition=result[i]; } //project to constraint boundary - try { - solver->satisfy(); - } catch (char const *str) { - dumpVPSCException(str,solver); - } - for (i=0;iposition(); - } - for (DummyVars::iterator it=dummy_vars.begin();it!=dummy_vars.end();++it){ - (*it)->updatePosition(); - } - // compute d, the vector from last pnt to projection pnt - for (i=0; ibetaCalc(numerator,denominator); - } - double beta = numerator/denominator; - - // beta > 1.0 takes us back outside the feasible region - // beta < 0 clearly not useful and may happen due to numerical imp. - if(beta>0&&beta<1.0) { - for (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; ifeasibleDescent(beta); + } +#ifdef CHECK_CONVERGENCE_BY_COST + /* This would be the slow way to detect convergence */ + //if(counter%2) { + double cost = computeCost(b,result); + printf(" gp[%d] %.15f %.15f\n",counter,previousCost,cost); + //COLA_ASSERT(previousCost>cost); + if(fabs(previousCost - cost) < tolerance) { + converged = true; } + previousCost = cost; + //} +#else + if(stepSizescale; } - double test=0; - for (i=0; iabsoluteDisplacement(); - } - if(test>tolerance) { - converged=false; - } - } + } destroyVPSC(solver); - return counter; + return counter; } // Setup an instance of the Variable Placement with Separation Constraints // for one iteration. @@ -168,70 +375,109 @@ unsigned GradientProjection::solve(double * b) { // global constraint list (including alignment constraints, // dir-edge constraints, containment constraints, etc). IncSolver* GradientProjection::setupVPSC() { - Constraint **cs; - //assert(lcs.size()==0); - - for(DummyVars::iterator dit=dummy_vars.begin(); - dit!=dummy_vars.end(); ++dit) { - (*dit)->setupVPSC(vars,lcs); - } - Variable** vs = new Variable*[vars.size()]; - for(unsigned i=0;igenerateNonOverlapConstraints(k,nonOverlapConstraints,*rs,vars,lcs); } else { - m=generateYConstraints(n,rs,vs,tmp_cs); - } - for(unsigned i=0;i::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 = new Constraint*[lcs.size() + gcs.size()]; - unsigned m = 0 ; - for(vector::iterator ci = lcs.begin();ci!=lcs.end();++ci) { - cs[m++] = *ci; - } - for(vector::iterator ci = gcs.begin();ci!=gcs.end();++ci) { - cs[m++] = *ci; - } - return new IncSolver(vars.size(),vs,m,cs); -} -void GradientProjection::clearDummyVars() { - for(DummyVars::iterator i=dummy_vars.begin();i!=dummy_vars.end();++i) { - delete *i; + 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(); ac!=acs->end();++ac) { - (*ac)->updatePosition(); + if(ccs) { + for(CompoundConstraints::const_iterator c=ccs->begin(); + c!=ccs->end();++c) { + (*c)->updatePosition(vpsc::XDIM); + (*c)->updatePosition(vpsc::YDIM); } } - unsigned m,n; - Constraint** cs = vpsc->getConstraints(m); - const Variable* const* vs = vpsc->getVariables(n); - delete vpsc; - delete [] cs; - delete [] vs; - for(vector::iterator i=lcs.begin();i!=lcs.end();++i) { - delete *i; + 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(); - //cout << " Vars count = " << vars.size() << " Dummy vars cnt=" << dummy_vars.size() << endl; - vars.resize(vars.size()-dummy_vars.size()*2); - for(DummyVars::iterator i=dummy_vars.begin();i!=dummy_vars.end();++i) { - DummyVarPair* p = *i; - delete p->left; - delete p->right; + 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); } } - -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4 : +} // namespace cola -- cgit v1.2.3