ardour/libs/cassowary/ClFDSolver.cc
Taybin Rutkin d09f6b3016 Initial revision
git-svn-id: svn://localhost/trunk/ardour2@4 d708f5d6-7413-0410-9779-e7cbd77b26cf
2005-05-13 20:47:18 +00:00

365 lines
9.2 KiB
C++

// $Id$
//
// Cassowary Incremental Constraint Solver
// Original Smalltalk Implementation by Alan Borning
// This C++ Implementation by Greg J. Badros, <gjb@cs.washington.edu>
// http://www.cs.washington.edu/homes/gjb
// (C) 1998, 1999 Greg J. Badros and Alan Borning
// See ../LICENSE for legal details regarding this software
//
// ClFDSolver.cc
#ifdef HAVE_CONFIG_H
#include <config.h>
#define CONFIG_H_INCLUDED
#endif
#include <cassowary/Cassowary.h>
#include <cassowary/ClFDSolver.h>
#include <cassowary/ClFDBinaryOneWayConstraint.h>
#include <cassowary/ClVariable.h>
#include <cassowary/debug.h>
#include <GTL/topsort.h>
#include <pair.h>
#include <math.h>
#include <stdarg.h>
static int fDebugFDSolve;
ClFDSolver &
ClFDSolver::AddConstraint(ClConstraint *const pcn)
{
AddConstraintInternal(pcn);
if (_fAutosolve) Solve();
return *this;
}
ClFDSolver &
ClFDSolver::RemoveConstraint(ClConstraint *const pcn)
{
RemoveConstraintInternal(pcn);
if (_fAutosolve) Solve();
return *this;
}
ClFDSolver &
ClFDSolver::AddConstraintInternal(ClConstraint *const pcn)
{
#ifdef CL_TRACE
Tracer TRACER(__FUNCTION__);
cerr << "(" << *pcn << ")" << endl;
#endif
ClFDBinaryOneWayConstraint *const pcnfd =
dynamic_cast<ClFDBinaryOneWayConstraint *const>(pcn);
if (!pcnfd) {
throw ExCLTooDifficultSpecial("Can only add ClFDBinaryOneWayConstraint-s to ClFDSolvers");
}
ClVariable rw = pcnfd->ClvRW();
ClVariable ro = pcnfd->ClvRO();
if (!rw.IsFDVariable()) {
throw ExCLTooDifficultSpecial("RW variable must be an FDVariable");
}
if (!(ro.IsNil() || ro.IsFDVariable())) {
throw ExCLTooDifficultSpecial("RO variable must be an FDVariable or clvNil");
}
// add the constraint to our set of cns
_setCns.insert(pcn);
// and add the constraint to the cns that affect var rw
assert(!rw.IsNil());
_mapClvToCns[rw].insert(pcn);
node nRw = GetVarNode(rw);
if (!ro.IsNil()) {
node nRo = GetVarNode(ro);
edge e = G.new_edge(nRo, nRw);
_mapCnToEdge[pcn] = e;
if (!G.is_acyclic()) {
/* there is a cycle... give up after cleaning up */
RemoveConstraint(pcn);
throw ExCLCycleNotAllowed();
}
}
return *this;
}
ClFDSolver &
ClFDSolver::RemoveConstraintInternal(ClConstraint *const pcn)
{
#ifdef CL_TRACE
Tracer TRACER(__FUNCTION__);
cerr << "(" << *pcn << ")" << endl;
#endif
ClFDBinaryOneWayConstraint *const pcnfd =
dynamic_cast<ClFDBinaryOneWayConstraint *const>(pcn);
if (!pcnfd) {
throw ExCLInternalError("Could not downcast to a ClFDBinaryOneWayConstraint");
}
ClConstraintSet::iterator itCn = _setCns.find(pcnfd);
if (itCn == _setCns.end()) {
throw ExCLConstraintNotFound();
}
_setCns.erase(itCn);
ClVariable rw = pcnfd->ClvRW();
ClVariable ro = pcnfd->ClvRO();
ClConstraintSet &_cnsAffectingRW = _mapClvToCns[rw];
ClConstraintSet::iterator it = _cnsAffectingRW.find(pcnfd);
if (it == _cnsAffectingRW.end()) {
throw ExCLInternalError("Cannot find pcnfd");
}
_cnsAffectingRW.erase(it);
if (!ro.IsNil()) {
edge e = _mapCnToEdge[pcn];
G.del_edge(e);
_mapCnToEdge.erase(pcn);
if (_mapVarToNode.find(ro) != _mapVarToNode.end() &&
_mapVarToNode[ro].degree() == 0) {
G.del_node(_mapVarToNode[ro]);
_mapVarToNode.erase(ro);
}
}
if (_mapVarToNode.find(rw) != _mapVarToNode.end() &&
_mapVarToNode[rw].degree() == 0) {
G.del_node(_mapVarToNode[rw]);
_mapVarToNode.erase(rw);
}
if (_mapClvToCns[rw].size() == 0) {
_mapClvToCns.erase(rw);
}
return *this;
}
ClFDSolver &
ClFDSolver::Solve()
{
topsort t;
t.run(G);
topsort::topsort_iterator it = t.top_order_begin();
topsort::topsort_iterator end = t.top_order_end();
ClSymbolicWeight errorTotal;
ResetSetFlagsOnVariables();
for (; it != end; ++it) {
ClVariable clv = nodeToVar[*it];
ClFDVariable *pcldv = dynamic_cast<ClFDVariable*>(clv.get_pclv());
#ifndef NO_FDSOLVE_DEBUG
if (fDebugFDSolve) {
if (!clv.IsNil()) cout << "node " << (*it) << " is " << clv << endl;
cerr << "Set from: " << endl;
for (ClConstraintSet::iterator itCns = _mapClvToCns[clv].begin();
itCns != _mapClvToCns[clv].end();
++itCns) {
const ClConstraint *pcn = *itCns;
cerr << *pcn << endl;
}
cerr << endl;
}
#endif
pair<ClSymbolicWeight,FDNumber> p = ComputeBest(pcldv);
ClSymbolicWeight e = p.first;
FDNumber v = p.second;
if (v == FDN_NOTSET)
throw ExCLRequiredFailure();
pcldv->ChangeValue(v);
pcldv->SetFIsSet(true);
errorTotal += e;
}
return *this;
}
/* return the best (lowest) incremental error and the value
at which that error occurs */
pair<ClSymbolicWeight,FDNumber>
ClFDSolver::ComputeBest(ClFDVariable *pcldv)
{
assert(pcldv);
// assert(!pcldv->FIsSet()); //GJB:FIXME::
ClSymbolicWeight minError = ClsRequired().symbolicWeight();
FDNumber bestValue = FDN_NOTSET;
// ClVariable clv(pcldv);
// for each domain value
for (list<FDNumber>::const_iterator itVal= pcldv->PlfdnDomain()->begin();
itVal != pcldv->PlfdnDomain()->end();
++itVal) {
FDNumber value = *itVal;
ClSymbolicWeight error;
const ClConstraintSet &setCns = _mapClvToCns[pcldv];
// for each constraint affecting *pcldv
for (ClConstraintSet::const_iterator itCn = setCns.begin();
itCn != setCns.end();
++itCn) {
const ClConstraint *pcn = *itCn;
ClSymbolicWeight e = ErrorForClvAtValSubjectToCn(pcldv,value,*pcn);
error += e;
}
// now error is the total error for binding clv <- value
if (error < minError) {
minError = error;
bestValue = value;
}
}
// now minError is the lowest error we can get for clv
// and it occurs binding clv <- bestValue
if (bestValue == FDN_NOTSET)
throw ExCLRequiredFailure();
return pair<ClSymbolicWeight,FDNumber>(minError,bestValue);
}
ClSymbolicWeight
ClFDSolver::ErrorForClvAtValSubjectToCn(ClFDVariable *pcldv,FDNumber value,const ClConstraint &cn)
{
const ClFDBinaryOneWayConstraint *pcnFd = dynamic_cast<const ClFDBinaryOneWayConstraint*>(&cn);
if (!pcnFd) throw ExCLInternalError("Not a binary FD constraint.");
ClCnRelation rel = pcnFd->Relation();
double m = pcnFd->Coefficient();
double b = pcnFd->Constant();
ClVariable rw = pcnFd->ClvRW();
ClVariable ro = pcnFd->ClvRO();
assert(rw.get_pclv() == pcldv);
double e;
double x = ro.IsNil()? 0 : ro.Value();
// return the error in satisfying:
// value REL m*x + b
double rhs = m*x + b;
switch (rel) {
case cnLEQ:
if (value <= rhs) e = 0;
else e = 1 + value-rhs;
break;
case cnLT:
if (value < rhs) e = 0;
else e = 1 + value-rhs;
break;
case cnGEQ:
if (value >= rhs) e = 0;
else e = 1+ rhs-value;
break;
case cnGT:
if (value > rhs) e = 0;
else e = 1 + rhs-value;
break;
case cnEQ:
if (value == rhs) e = 0;
else e = 1 + fabs(rhs-value);
break;
case cnNEQ:
if (value != rhs) e = 0;
else e = 1; /* GJB:FIXME:: what makes sense here? */
break;
default:
e = 0; /* quiet warning */
assert(false);
}
ClSymbolicWeight err;
if (cn.IsRequired() && e > 0)
err = ClsRequired().symbolicWeight();
else
err = cn.symbolicWeight() * (e*cn._weight);
#ifndef NO_FDSOLVE_DEBUG
if (fDebugFDSolve) {
cerr << "Error at " << value << " = " << err << endl;
}
#endif
return err;
}
ClFDSolver &
ClFDSolver::ShowSolve()
{
topsort t;
t.run(G);
topsort::topsort_iterator it = t.top_order_begin();
topsort::topsort_iterator end = t.top_order_end();
for (; it != end; ++it) {
ClVariable clv = nodeToVar[*it];
if (!clv.IsNil()) cout << "Node " << (*it) << " is " << clv << endl;
cout << "Set from: " << endl;
for (ClConstraintSet::iterator itCns = _mapClvToCns[clv].begin();
itCns != _mapClvToCns[clv].end();
++itCns) {
const ClConstraint *pcn = *itCns;
cout << *pcn << endl;
}
cout << endl;
}
return *this;
}
/* Turn all FDVariable FIsSet() flags to false */
void
ClFDSolver::ResetSetFlagsOnVariables()
{
for (ClVarToConstraintSetMap::iterator it = _mapClvToCns.begin();
it != _mapClvToCns.end();
++it) {
ClVariable clv = (*it).first;
ClFDVariable *pcldv = dynamic_cast<ClFDVariable*>(clv.get_pclv());
assert(pcldv);
pcldv->SetFIsSet(false);
}
}
ostream &
ClFDSolver::PrintOn(ostream &xo) const
{
xo << "FDSolver: "
<< _setCns
<< "Graph nodes, edges = " << G.number_of_nodes() << ", " << G.number_of_edges()
<< endl;
return xo;
}
ostream &
ClFDSolver::PrintInternalInfo(ostream &xo) const
{ return xo; }
ostream &operator<<(ostream &xo, const ClFDSolver &clfds)
{ return clfds.PrintOn(xo); }
//// protected member functions
/* Create node for v in G, if necessary,
otherwise return the node we already created. */
node
ClFDSolver::GetVarNode(ClVariable v)
{
ClMap<ClVariable,node>::iterator it = _mapVarToNode.find(v);
if (it == _mapVarToNode.end()) {
node n = G.new_node();
_mapVarToNode[v] = n;
nodeToVar[n] = v;
return n;
} else {
return (*it).second;
}
}
void
ListPushOnto(list<FDNumber> *pl, ...)
{
va_list ap;
va_start(ap, pl);
FDNumber n;
while ( (n = va_arg(ap, FDNumber)) != FDN_EOL) {
pl->push_back(n);
}
va_end(ap);
}