summaryrefslogtreecommitdiff
path: root/libs/cassowary/ClFDSolver.cc
blob: 7f2d19986956497fda91a34cf18f7cfe983be9c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// $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);
}