diff options
| author | Tim Dwyer <tgdwyer@gmail.com> | 2006-07-12 00:55:58 +0000 |
|---|---|---|
| committer | tgdwyer <tgdwyer@users.sourceforge.net> | 2006-07-12 00:55:58 +0000 |
| commit | 12b21e1d27f43deaa748419919b40b80cedd0ddd (patch) | |
| tree | 9748126a763c5a10b9ee25401cf2463a65a2aed6 /src/libvpsc/solve_VPSC.cpp | |
| parent | update (diff) | |
| download | inkscape-12b21e1d27f43deaa748419919b40b80cedd0ddd.tar.gz inkscape-12b21e1d27f43deaa748419919b40b80cedd0ddd.zip | |
Previously graph layout was done using the Kamada-Kawai layout algorithm
implemented in Boost. I am replacing this with a custom implementation of
a constrained stress-majorization algorithm.
The stress-majorization algorithm is more robust and has better convergence
characteristics than Kamada-Kawai, and also simple constraints can be placed
on node position (for example, to enforce downward-pointing edges, non-overlap constraints, or cluster constraints).
Another big advantage is that we no longer need Boost.
I've tested the basic functionality, but I have yet to properly handle
disconnected graphs or to properly scale the resulting layout.
This commit also includes significant refactoring... the quadratic program solver - libvpsc (Variable Placement with Separation Constraints) has been moved to src/libvpsc and the actual graph layout algorithm is in libcola.
(bzr r1394)
Diffstat (limited to 'src/libvpsc/solve_VPSC.cpp')
| -rw-r--r-- | src/libvpsc/solve_VPSC.cpp | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/src/libvpsc/solve_VPSC.cpp b/src/libvpsc/solve_VPSC.cpp new file mode 100644 index 000000000..44918951d --- /dev/null +++ b/src/libvpsc/solve_VPSC.cpp @@ -0,0 +1,417 @@ +/** + * \brief Solve an instance of the "Variable Placement with Separation + * Constraints" problem. + * + * Authors: + * Tim Dwyer <tgdwyer@gmail.com> + * + * Copyright (C) 2005 Authors + * + * Released under GNU LGPL. Read the file 'COPYING' for more information. + */ + +#include <cassert> +#include "constraint.h" +#include "block.h" +#include "blocks.h" +#include "solve_VPSC.h" +#include <math.h> +#include <sstream> +#ifdef RECTANGLE_OVERLAP_LOGGING +#include <fstream> +using std::ios; +using std::ofstream; +using std::endl; +#endif + +using std::ostringstream; +using std::list; +using std::set; + +static const double ZERO_UPPERBOUND=-0.0000001; + +IncVPSC::IncVPSC(const unsigned n, Variable* const vs[], const unsigned m, Constraint *cs[]) + : VPSC(n,vs,m,cs) { + inactive.assign(cs,cs+m); + for(ConstraintList::iterator i=inactive.begin();i!=inactive.end();++i) { + (*i)->active=false; + } +} +VPSC::VPSC(const unsigned n, Variable* const vs[], const unsigned m, Constraint *cs[]) : m(m), cs(cs), n(n), vs(vs) { + bs=new Blocks(n, vs); +#ifdef RECTANGLE_OVERLAP_LOGGING + printBlocks(); + assert(!constraintGraphIsCyclic(n,vs)); +#endif +} +VPSC::~VPSC() { + delete bs; +} + +// useful in debugging +void VPSC::printBlocks() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + for(set<Block*>::iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + f<<" "<<*b<<endl; + } + for(unsigned i=0;i<m;i++) { + f<<" "<<*cs[i]<<endl; + } +#endif +} +/** +* Produces a feasible - though not necessarily optimal - solution by +* examining blocks in the partial order defined by the directed acyclic +* graph of constraints. For each block (when processing left to right) we +* maintain the invariant that all constraints to the left of the block +* (incoming constraints) are satisfied. This is done by repeatedly merging +* blocks into bigger blocks across violated constraints (most violated +* first) fixing the position of variables inside blocks relative to one +* another so that constraints internal to the block are satisfied. +*/ +void VPSC::satisfy() { + list<Variable*> *vs=bs->totalOrder(); + for(list<Variable*>::iterator i=vs->begin();i!=vs->end();++i) { + Variable *v=*i; + if(!v->block->deleted) { + bs->mergeLeft(v->block); + } + } + bs->cleanup(); + for(unsigned i=0;i<m;i++) { + if(cs[i]->slack() < ZERO_UPPERBOUND) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Error: Unsatisfied constraint: "<<*cs[i]<<endl; +#endif + //assert(cs[i]->slack()>-0.0000001); + throw "Unsatisfied constraint"; + } + } + delete vs; +} + +void VPSC::refine() { + bool solved=false; + // Solve shouldn't loop indefinately + // ... but just to make sure we limit the number of iterations + unsigned maxtries=100; + while(!solved&&maxtries>=0) { + solved=true; + maxtries--; + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + b->setUpInConstraints(); + b->setUpOutConstraints(); + } + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + Constraint *c=b->findMinLM(); + if(c!=NULL && c->lm<0) { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Split on constraint: "<<*c<<endl; +#endif + // Split on c + Block *l=NULL, *r=NULL; + bs->split(b,l,r,c); + bs->cleanup(); + // split alters the block set so we have to restart + solved=false; + break; + } + } + } + for(unsigned i=0;i<m;i++) { + if(cs[i]->slack() < ZERO_UPPERBOUND) { + assert(cs[i]->slack()>ZERO_UPPERBOUND); + throw "Unsatisfied constraint"; + } + } +} +/** + * Calculate the optimal solution. After using satisfy() to produce a + * feasible solution, refine() examines each block to see if further + * refinement is possible by splitting the block. This is done repeatedly + * until no further improvement is possible. + */ +void VPSC::solve() { + satisfy(); + refine(); +} + +void IncVPSC::solve() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"solve_inc()..."<<endl; +#endif + double lastcost,cost = bs->cost(); + do { + lastcost=cost; + satisfy(); + splitBlocks(); + cost = bs->cost(); +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" cost="<<cost<<endl; +#endif + } while(fabs(lastcost-cost)>0.0001); +} +/** + * incremental version of satisfy that allows refinement after blocks are + * moved. + * + * - move blocks to new positions + * - repeatedly merge across most violated constraint until no more + * violated constraints exist + * + * Note: there is a special case to handle when the most violated constraint + * is between two variables in the same block. Then, we must split the block + * over an active constraint between the two variables. We choose the + * constraint with the most negative lagrangian multiplier. + */ +void IncVPSC::satisfy() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"satisfy_inc()..."<<endl; +#endif + splitBlocks(); + long splitCtr = 0; + Constraint* v = NULL; + while((v=mostViolated(inactive))&&(v->equality || v->slack() < ZERO_UPPERBOUND)) { + assert(!v->active); + Block *lb = v->left->block, *rb = v->right->block; + if(lb != rb) { + lb->merge(rb,v); + } else { + if(splitCtr++>10000) { + throw "Cycle Error!"; + } + // constraint is within block, need to split first + inactive.push_back(lb->splitBetween(v->left,v->right,lb,rb)); + lb->merge(rb,v); + bs->insert(lb); + } + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" finished merges."<<endl; +#endif + bs->cleanup(); + for(unsigned i=0;i<m;i++) { + v=cs[i]; + if(v->slack() < ZERO_UPPERBOUND) { + ostringstream s; + s<<"Unsatisfied constraint: "<<*v; +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<s.str()<<endl; +#endif + throw s.str().c_str(); + } + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" finished cleanup."<<endl; + printBlocks(); +#endif +} +void IncVPSC::moveBlocks() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"moveBlocks()..."<<endl; +#endif + for(set<Block*>::const_iterator i(bs->begin());i!=bs->end();++i) { + Block *b = *i; + b->wposn = b->desiredWeightedPosition(); + b->posn = b->wposn / b->weight; + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" moved blocks."<<endl; +#endif +} +void IncVPSC::splitBlocks() { +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); +#endif + moveBlocks(); + splitCnt=0; + // Split each block if necessary on min LM + for(set<Block*>::const_iterator i(bs->begin());i!=bs->end();++i) { + Block* b = *i; + Constraint* v=b->findMinLM(); + if(v!=NULL && v->lm < ZERO_UPPERBOUND) { + assert(!v->equality); +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" found split point: "<<*v<<" lm="<<v->lm<<endl; +#endif + splitCnt++; + Block *b = v->left->block, *l=NULL, *r=NULL; + assert(v->left->block == v->right->block); + double pos = b->posn; + b->split(l,r,v); + l->posn=r->posn=pos; + l->wposn = l->posn * l->weight; + r->wposn = r->posn * r->weight; + bs->insert(l); + bs->insert(r); + b->deleted=true; + inactive.push_back(v); +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" new blocks: "<<*l<<" and "<<*r<<endl; +#endif + } + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" finished splits."<<endl; +#endif + bs->cleanup(); +} + +/** + * Scan constraint list for the most violated constraint, or the first equality + * constraint + */ +Constraint* IncVPSC::mostViolated(ConstraintList &l) { + double minSlack = DBL_MAX; + Constraint* v=NULL; +#ifdef RECTANGLE_OVERLAP_LOGGING + ofstream f(LOGFILE,ios::app); + f<<"Looking for most violated..."<<endl; +#endif + ConstraintList::iterator end = l.end(); + ConstraintList::iterator deletePoint = end; + for(ConstraintList::iterator i=l.begin();i!=end;++i) { + Constraint *c=*i; + double slack = c->slack(); + if(c->equality || slack < minSlack) { + minSlack=slack; + v=c; + deletePoint=i; + if(c->equality) break; + } + } + // Because the constraint list is not order dependent we just + // move the last element over the deletePoint and resize + // downwards. There is always at least 1 element in the + // vector because of search. + if(deletePoint != end && (minSlack<ZERO_UPPERBOUND||v->equality)) { + *deletePoint = l[l.size()-1]; + l.resize(l.size()-1); + } +#ifdef RECTANGLE_OVERLAP_LOGGING + f<<" most violated is: "<<*v<<endl; +#endif + return v; +} + +#include <map> +using std::map; +using std::vector; +struct node { + set<node*> in; + set<node*> out; +}; +// useful in debugging - cycles would be BAD +bool VPSC::constraintGraphIsCyclic(const unsigned n, Variable *vs[]) { + map<Variable*, node*> varmap; + vector<node*> graph; + for(unsigned i=0;i<n;i++) { + node *u=new node; + graph.push_back(u); + varmap[vs[i]]=u; + } + for(unsigned i=0;i<n;i++) { + for(vector<Constraint*>::iterator c=vs[i]->in.begin();c!=vs[i]->in.end();++c) { + Variable *l=(*c)->left; + varmap[vs[i]]->in.insert(varmap[l]); + } + + for(vector<Constraint*>::iterator c=vs[i]->out.begin();c!=vs[i]->out.end();++c) { + Variable *r=(*c)->right; + varmap[vs[i]]->out.insert(varmap[r]); + } + } + while(graph.size()>0) { + node *u=NULL; + vector<node*>::iterator i=graph.begin(); + for(;i!=graph.end();++i) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set<node*>::iterator j=u->out.begin();j!=u->out.end();++j) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i<graph.size(); ++i) { + delete graph[i]; + } + return false; +} + +// useful in debugging - cycles would be BAD +bool VPSC::blockGraphIsCyclic() { + map<Block*, node*> bmap; + vector<node*> graph; + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + node *u=new node; + graph.push_back(u); + bmap[b]=u; + } + for(set<Block*>::const_iterator i=bs->begin();i!=bs->end();++i) { + Block *b=*i; + b->setUpInConstraints(); + Constraint *c=b->findMinInConstraint(); + while(c!=NULL) { + Block *l=c->left->block; + bmap[b]->in.insert(bmap[l]); + b->deleteMinInConstraint(); + c=b->findMinInConstraint(); + } + + b->setUpOutConstraints(); + c=b->findMinOutConstraint(); + while(c!=NULL) { + Block *r=c->right->block; + bmap[b]->out.insert(bmap[r]); + b->deleteMinOutConstraint(); + c=b->findMinOutConstraint(); + } + } + while(graph.size()>0) { + node *u=NULL; + vector<node*>::iterator i=graph.begin(); + for(;i!=graph.end();++i) { + u=*i; + if(u->in.size()==0) { + break; + } + } + if(i==graph.end() && graph.size()>0) { + //cycle found! + return true; + } else { + graph.erase(i); + for(set<node*>::iterator j=u->out.begin();j!=u->out.end();++j) { + node *v=*j; + v->in.erase(u); + } + delete u; + } + } + for(unsigned i=0; i<graph.size(); i++) { + delete graph[i]; + } + return false; +} + |
