123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- /**
- * \brief A block is a group of variables that must be moved together to improve
- * the goal function without violating already active constraints.
- * The variables in a block are spanned by a tree of active constraints.
- *
- * Authors:
- * Tim Dwyer <[email protected]>
- *
- * Copyright (C) 2005 Authors
- *
- * This version is released under the CPL (Common Public License) with
- * the Graphviz distribution.
- * A version is also available under the LGPL as part of the Adaptagrams
- * project: https://github.com/mjwybrow/adaptagrams.
- * If you make improvements or bug fixes to this code it would be much
- * appreciated if you could also contribute those changes back to the
- * Adaptagrams repository.
- */
- #include <algorithm>
- #include <cassert>
- #include <ostream>
- #include <vector>
- #include <vpsc/constraint.h>
- #include <vpsc/block.h>
- #include <vpsc/blocks.h>
- #include <fstream>
- using std::ios;
- using std::ofstream;
- using std::vector;
- #ifndef RECTANGLE_OVERLAP_LOGGING
- #define RECTANGLE_OVERLAP_LOGGING 0
- #endif
- /// `>` comparator for constraints
- static bool gt(const Constraint *const lhs, const Constraint *const rhs) {
- return compareConstraints(rhs, lhs);
- }
- /// create a heap within a vector
- ///
- /// The standard library’s heap functionality is all structured around creating
- /// a max-heap but we want a min-heap. So we flip the comparator we give it.
- static void make_heap(std::vector<Constraint *> &heap) {
- std::make_heap(heap.begin(), heap.end(), gt);
- }
- /// add all elements from `heap2` into the heap `heap1`
- static void merge_heaps(std::vector<Constraint *> &heap1,
- const std::vector<Constraint *> &heap2) {
- heap1.insert(heap1.end(), heap2.begin(), heap2.end());
- make_heap(heap1);
- }
- /// get the minimum heap element
- static Constraint *findMin(std::vector<Constraint *> &heap) {
- assert(std::is_heap(heap.begin(), heap.end(), gt));
- return heap.front();
- }
- /// remove the minimum heap element
- static void deleteMin(std::vector<Constraint *> &heap) {
- assert(std::is_heap(heap.begin(), heap.end(), gt));
- std::pop_heap(heap.begin(), heap.end(), gt);
- heap.pop_back();
- }
- /// add an item to a heap
- static void insert(std::vector<Constraint *> &heap, Constraint *c) {
- assert(std::is_heap(heap.begin(), heap.end(), gt));
- heap.push_back(c);
- std::push_heap(heap.begin(), heap.end(), gt);
- }
- void Block::addVariable(Variable *v) {
- v->block=this;
- vars.push_back(v);
- weight+=v->weight;
- wposn += v->weight * (v->desiredPosition - v->offset);
- posn=wposn/weight;
- }
- Block::Block(Variable *v) {
- timeStamp=0;
- posn=weight=wposn=0;
- deleted=false;
- if(v!=nullptr) {
- v->offset=0;
- addVariable(v);
- }
- }
- double Block::desiredWeightedPosition() {
- double wp = 0;
- for (const Variable *v : vars) {
- wp += (v->desiredPosition - v->offset) * v->weight;
- }
- return wp;
- }
- void Block::setUpInConstraints() {
- in = setUpConstraintHeap(true);
- }
- void Block::setUpOutConstraints() {
- out = setUpConstraintHeap(false);
- }
- std::vector<Constraint *> Block::setUpConstraintHeap(bool use_in) {
- std::vector<Constraint *> h;
- for (Variable *v : vars) {
- vector<Constraint*> *cs= use_in ? &v->in : &v->out;
- for (Constraint *c : *cs) {
- c->timeStamp=blockTimeCtr;
- if ((c->left->block != this && use_in) || (c->right->block != this && !use_in)) {
- h.push_back(c);
- }
- }
- }
- make_heap(h);
- return h;
- }
- void Block::merge(Block* b, Constraint* c) {
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" merging on: "<<*c<<",c->left->offset="<<c->left->offset<<",c->right->offset="<<c->right->offset<<"\n";
- }
- const double dist = c->right->offset - c->left->offset - c->gap;
- Block *l=c->left->block;
- Block *r=c->right->block;
- if (vars.size() < b->vars.size()) {
- r->merge(l,c,dist);
- } else {
- l->merge(r,c,-dist);
- }
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" merged block="<<(b->deleted?*this:*b)<<"\n";
- }
- }
- /**
- * Merges b into this block across c. Can be either a
- * right merge or a left merge
- * @param b block to merge into this
- * @param c constraint being merged
- * @param distance separation required to satisfy c
- */
- void Block::merge(Block *b, Constraint *c, double dist) {
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" merging: "<<*b<<"dist="<<dist<<"\n";
- }
- c->active=true;
- wposn+=b->wposn-dist*b->weight;
- weight+=b->weight;
- posn=wposn/weight;
- for (Variable *v : b->vars) {
- v->block=this;
- v->offset+=dist;
- vars.push_back(v);
- }
- b->deleted=true;
- }
- void Block::mergeIn(Block *b) {
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" merging constraint heaps... \n";
- }
- // We check the top of the heaps to remove possible internal constraints
- findMinInConstraint();
- b->findMinInConstraint();
- merge_heaps(in, b->in);
- }
- void Block::mergeOut(Block *b) {
- findMinOutConstraint();
- b->findMinOutConstraint();
- merge_heaps(out, b->out);
- }
- Constraint *Block::findMinInConstraint() {
- Constraint *v = nullptr;
- vector<Constraint*> outOfDate;
- while (!in.empty()) {
- v = findMin(in);
- const Block *lb = v->left->block;
- const Block *rb = v->right->block;
- // rb may not be this if called between merge and mergeIn
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" checking constraint ... "<<*v;
- f<<" timestamps: left="<<lb->timeStamp<<" right="<<rb->timeStamp<<" constraint="<<v->timeStamp<<"\n";
- }
- if(lb == rb) {
- // constraint has been merged into the same block
- if(RECTANGLE_OVERLAP_LOGGING && v->slack()<0) {
- ofstream f(LOGFILE,ios::app);
- f<<" violated internal constraint found! "<<*v<<"\n";
- f<<" lb="<<*lb<<"\n";
- f<<" rb="<<*rb<<"\n";
- }
- deleteMin(in);
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" ... skipping internal constraint\n";
- }
- } else if(v->timeStamp < lb->timeStamp) {
- // block at other end of constraint has been moved since this
- deleteMin(in);
- outOfDate.push_back(v);
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" reinserting out of date (reinsert later)\n";
- }
- } else {
- break;
- }
- }
- for (Constraint *c : outOfDate) {
- c->timeStamp=blockTimeCtr;
- insert(in, c);
- }
- if(in.empty()) {
- v=nullptr;
- } else {
- v = findMin(in);
- }
- return v;
- }
- Constraint *Block::findMinOutConstraint() {
- if(out.empty()) return nullptr;
- Constraint *v = findMin(out);
- while (v->left->block == v->right->block) {
- deleteMin(out);
- if(out.empty()) return nullptr;
- v = findMin(out);
- }
- return v;
- }
- void Block::deleteMinInConstraint() {
- deleteMin(in);
- }
- void Block::deleteMinOutConstraint() {
- deleteMin(out);
- }
- inline bool Block::canFollowLeft(const Constraint *c, const Variable *last) {
- return c->left->block==this && c->active && last!=c->left;
- }
- inline bool Block::canFollowRight(const Constraint *c, const Variable *last) {
- return c->right->block==this && c->active && last!=c->right;
- }
- // computes the derivative of v and the lagrange multipliers
- // of v's out constraints (as the recursive sum of those below.
- // Does not backtrack over u.
- // also records the constraint with minimum lagrange multiplier
- // in min_lm
- double Block::compute_dfdv(Variable *v, Variable *u, Constraint *&min_lm) {
- double dfdv=v->weight*(v->position() - v->desiredPosition);
- for (Constraint *c : v->out) {
- if(canFollowRight(c,u)) {
- dfdv+=c->lm=compute_dfdv(c->right,v,min_lm);
- if(min_lm==nullptr||c->lm<min_lm->lm) min_lm=c;
- }
- }
- for (Constraint *c : v->in) {
- if(canFollowLeft(c,u)) {
- dfdv-=c->lm=-compute_dfdv(c->left,v,min_lm);
- if(min_lm==nullptr||c->lm<min_lm->lm) min_lm=c;
- }
- }
- return dfdv;
- }
- // computes dfdv for each variable and uses the sum of dfdv on either side of
- // the constraint c to compute the lagrangian multiplier lm_c.
- // The top level v and r are variables between which we want to find the
- // constraint with the smallest lm.
- // When we find r we pass nullptr to subsequent recursive calls,
- // thus r=nullptr indicates constraints are not on the shortest path.
- // Similarly, m is initially nullptr and is only assigned a value if the next
- // variable to be visited is r or if a possible min constraint is returned from
- // a nested call (rather than nullptr).
- // Then, the search for the m with minimum lm occurs as we return from
- // the recursion (checking only constraints traversed left-to-right
- // in order to avoid creating any new violations).
- Block::Pair Block::compute_dfdv_between(Variable* r, Variable* v, Variable* u,
- Direction dir = NONE, bool changedDirection = false) {
- double dfdv=v->weight*(v->position() - v->desiredPosition);
- Constraint *m=nullptr;
- for (Constraint *c : v->in) {
- if(canFollowLeft(c,u)) {
- if(dir==RIGHT) {
- changedDirection = true;
- }
- if(c->left==r) {
- r=nullptr; m=c;
- }
- Pair p=compute_dfdv_between(r,c->left,v,
- LEFT,changedDirection);
- dfdv -= c->lm = -p.first;
- if(r && p.second)
- m = p.second;
- }
- }
- for (Constraint *c : v->out) {
- if(canFollowRight(c,u)) {
- if(dir==LEFT) {
- changedDirection = true;
- }
- if(c->right==r) {
- r=nullptr; m=c;
- }
- Pair p=compute_dfdv_between(r,c->right,v,
- RIGHT,changedDirection);
- dfdv += c->lm = p.first;
- if(r && p.second)
- m = changedDirection && c->lm < p.second->lm
- ? c
- : p.second;
- }
- }
- return Pair(dfdv,m);
- }
- // resets LMs for all active constraints to 0 by
- // traversing active constraint tree starting from v,
- // not back tracking over u
- void Block::reset_active_lm(Variable *v, Variable *u) {
- for (Constraint *c : v->out) {
- if(canFollowRight(c,u)) {
- c->lm=0;
- reset_active_lm(c->right,v);
- }
- }
- for (Constraint *c : v->in) {
- if(canFollowLeft(c,u)) {
- c->lm=0;
- reset_active_lm(c->left,v);
- }
- }
- }
- /**
- * finds the constraint with the minimum lagrange multiplier, that is, the constraint
- * that most wants to split
- */
- Constraint *Block::findMinLM() {
- Constraint *min_lm=nullptr;
- reset_active_lm(vars.front(),nullptr);
- compute_dfdv(vars.front(),nullptr,min_lm);
- return min_lm;
- }
- Constraint *Block::findMinLMBetween(Variable* lv, Variable* rv) {
- Constraint *min_lm=nullptr;
- reset_active_lm(vars.front(),nullptr);
- min_lm=compute_dfdv_between(rv,lv,nullptr).second;
- return min_lm;
- }
- // populates block b by traversing the active constraint tree adding variables as they're
- // visited. Starts from variable v and does not backtrack over variable u.
- void Block::populateSplitBlock(Block *b, Variable *v, Variable *u) {
- b->addVariable(v);
- for (Constraint *c : v->in) {
- if (canFollowLeft(c,u))
- populateSplitBlock(b, c->left, v);
- }
- for (Constraint *c : v->out) {
- if (canFollowRight(c,u))
- populateSplitBlock(b, c->right, v);
- }
- }
- /**
- * Block needs to be split because of a violated constraint between vl and vr.
- * We need to search the active constraint tree between l and r and find the constraint
- * with min lagrangrian multiplier and split at that point.
- * Returns the split constraint
- */
- Constraint* Block::splitBetween(Variable* vl, Variable* vr, Block* &lb, Block* &rb) {
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" need to split between: "<<*vl<<" and "<<*vr<<"\n";
- }
- Constraint *c=findMinLMBetween(vl, vr);
- if (RECTANGLE_OVERLAP_LOGGING) {
- ofstream f(LOGFILE,ios::app);
- f<<" going to split on: "<<*c<<"\n";
- }
- split(lb,rb,c);
- deleted = true;
- return c;
- }
- /**
- * Creates two new blocks, l and r, and splits this block across constraint c,
- * placing the left subtree of constraints (and associated variables) into l
- * and the right into r.
- */
- void Block::split(Block* &l, Block* &r, Constraint* c) {
- c->active=false;
- l=new Block();
- populateSplitBlock(l,c->left,c->right);
- r=new Block();
- populateSplitBlock(r,c->right,c->left);
- }
- /**
- * Computes the cost (squared euclidean distance from desired positions) of the
- * current positions for variables in this block
- */
- double Block::cost() {
- double c = 0;
- for (const Variable *v : vars) {
- const double diff = v->position() - v->desiredPosition;
- c += v->weight * diff * diff;
- }
- return c;
- }
- std::ostream& operator <<(std::ostream &os, const Block &b) {
- os<<"Block:";
- for(const Variable *v : b.vars) {
- os<<" "<<*v;
- }
- if(b.deleted) {
- os<<" Deleted!";
- }
- return os;
- }
|