iv.nanovega.textlayouter: more API!
[iv.d.git] / cassowary.d
blob6b3be03936c39318c7ca13032418a056c28c5b26
1 /*
2 * Cassowary.D: an incremental constraint solver for D
4 * Copyright (C) 2005-2006 Jo Vermeulen (jo.vermeulen@uhasselt.be)
5 * Converted to D by Ketmar // Invisible Vector (ketmar@ketmar.no-ip.org)
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 module iv.cassowary /*is aliced*/;
21 import iv.alice;
24 // ////////////////////////////////////////////////////////////////////////// //
25 alias CswNumber = double;
26 alias CswStrength = CswNumber;
29 // ////////////////////////////////////////////////////////////////////////// //
30 private mixin template CswErrorErrorBody() {
31 @safe pure nothrow this (string msg, string file=__FILE__, usize line=__LINE__, Throwable next=null) {
32 super(msg, file, line, next);
37 class CswError : Exception { mixin CswErrorErrorBody; }
40 private enum error(string name) = `class CswError`~name~` : CswError { mixin CswErrorErrorBody; }`;
42 mixin(error!("ConstraintNotFound"));
43 mixin(error!("InternalError"));
44 mixin(error!("NonlinearExpression"));
45 mixin(error!("NotEnoughStays"));
46 mixin(error!("RequiredFailure"));
47 mixin(error!("TooDifficult"));
48 mixin(error!("Parser"));
49 mixin(error!("NoVariable"));
50 mixin(error!("InvalidMathOp"));
53 // ////////////////////////////////////////////////////////////////////////// //
54 /// The enumerations from CswLinearInequality,
55 /// and `global' functions that we want easy to access
56 abstract class Csw {
57 private import std.traits : isSomeString;
59 final:
60 static:
61 enum CompOp {
62 GEQ = 1,
63 LEQ = 2
66 CswLinearExpression plus (CswLinearExpression e1, CswLinearExpression e2) nothrow { return e1.plus(e2); }
67 CswLinearExpression plus (CswNumber e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).plus(e2); }
68 CswLinearExpression plus (CswVariable e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).plus(e2); }
69 CswLinearExpression plus (CswLinearExpression e1, CswVariable e2) nothrow { return e1.plus(new CswLinearExpression(e2)); }
70 CswLinearExpression plus (CswVariable e1, CswNumber e2) nothrow { return (new CswLinearExpression(e1)).plus(new CswLinearExpression(e2)); }
71 CswLinearExpression plus (CswNumber e1, CswVariable e2) nothrow { return (new CswLinearExpression(e1)).plus(new CswLinearExpression(e2)); }
72 CswLinearExpression minus (CswLinearExpression e1, CswLinearExpression e2) nothrow { return e1.minus(e2); }
73 CswLinearExpression minus (CswNumber e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).minus(e2); }
74 CswLinearExpression minus (CswLinearExpression e1, CswNumber e2) nothrow { return e1.minus(new CswLinearExpression(e2)); }
75 CswLinearExpression times (CswLinearExpression e1, CswLinearExpression e2) { return e1.times(e2); }
76 CswLinearExpression times (CswLinearExpression e1, CswVariable e2) { return e1.times(new CswLinearExpression(e2)); }
77 CswLinearExpression times (CswVariable e1, CswLinearExpression e2) { return (new CswLinearExpression(e1)).times(e2); }
78 CswLinearExpression times (CswLinearExpression e1, CswNumber e2) { return e1.times(new CswLinearExpression(e2)); }
79 CswLinearExpression times (CswNumber e1, CswLinearExpression e2) { return (new CswLinearExpression(e1)).times(e2); }
80 CswLinearExpression times (CswNumber n, CswVariable clv) nothrow { return new CswLinearExpression(clv, n); }
81 CswLinearExpression times (CswVariable clv, CswNumber n) nothrow { return new CswLinearExpression(clv, n); }
82 CswLinearExpression divide (CswLinearExpression e1, CswLinearExpression e2) { return e1.divide(e2); }
84 bool approx (CswNumber a, CswNumber b) pure @safe nothrow @nogc {
85 import std.math : abs;
86 enum CswNumber epsilon = 1.0e-8;
87 if (a == 0.0) return (abs(b) < epsilon);
88 if (b == 0.0) return (abs(a) < epsilon);
89 return (abs(a-b) < abs(a)*epsilon);
92 bool approx (CswVariable clv, CswNumber b) pure @safe nothrow @nogc { return approx(clv.value, b); }
93 bool approx (CswNumber a, CswVariable clv) pure @safe nothrow @nogc { return approx(a, clv.value); }
95 CswStrength Strength (string name) @safe nothrow @nogc {
96 switch (name) {
97 case "required": return Csw.Required;
98 case "strong": return Csw.Strong;
99 case "medium": return Csw.Medium;
100 case "weak": return Csw.Weak;
101 default: assert(0, "invalid strength name");
105 private enum SWMult = 1000.0;
106 CswNumber Strength (in CswNumber w1, in CswNumber w2, in CswNumber w3) @safe nothrow @nogc { return w3+w2*SWMult+w1*(SWMult*SWMult); }
108 enum Required = Strength(1000, 1000, 1000);
109 enum Strong = Strength(1, 0, 0);
110 enum Medium = Strength(0, 1, 0);
111 enum Weak = Strength(0, 0, 1);
113 private bool isRequiredStrength (CswStrength str) @safe pure nothrow @nogc { return (str >= Required); }
117 // ////////////////////////////////////////////////////////////////////////// //
118 // constraints
119 class CswConstraint {
120 private:
121 static uint mCstIndex;
123 @property static uint newIndex () @trusted nothrow @nogc {
124 if (++mCstIndex == 0) assert(0, "out of constraint indexes");
125 return mCstIndex;
128 uint cindex;
129 CswStrength mStrength = void;
130 CswNumber mWeight;
132 public:
133 override string toString () const {
134 import std.string : format;
135 // example output: weak:[0,0,1] {1} (23 + -1*[update.height:23]
136 return format("%s {%s} (%s", mStrength, weight, expressionStr);
139 abstract @property string expressionStr () const;
141 @nogc:
142 @safe:
143 nothrow:
144 this (in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
145 cindex = newIndex;
146 mStrength = strength;
147 mWeight = weight;
150 abstract @property CswLinearExpression expression ();
152 pure {
153 @property bool isEditConstraint () const { return false; }
154 @property bool isInequality () const { return false; }
155 @property bool isRequired () const { return Csw.isRequiredStrength(mStrength); }
156 @property bool isStayConstraint () const { return false; }
159 final:
160 @property ref CswStrength strength () { return mStrength; }
161 @property void strength (in CswStrength v) { mStrength = v; }
163 @property CswNumber weight () const pure { return mWeight; }
164 @property void weight (CswNumber v) { mWeight = v; }
168 class CswEditOrStayConstraint : CswConstraint {
169 protected CswVariable mVariable;
170 // cache the expression
171 private CswLinearExpression mExpression;
173 public:
174 // add missing bracket -> see CswConstraint#ToString(...)
175 override string toString () const { return super.toString()~")"; }
176 override @property string expressionStr () const { return mExpression.toString; }
178 @safe:
179 nothrow:
180 this (CswVariable var, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
181 super(strength, weight);
182 mVariable = var;
183 mExpression = new CswLinearExpression(mVariable, -1.0, mVariable.value);
186 @nogc:
187 final @property CswVariable variable () pure { return mVariable; }
188 override @property CswLinearExpression expression () pure { return mExpression; }
192 class CswEditConstraint : CswEditOrStayConstraint {
193 override string toString () const { return "edit"~super.toString(); }
194 @safe:
195 nothrow:
196 this (CswVariable clv, in CswStrength strength=Csw.Required, CswNumber weight=1.0) { super(clv, strength, weight); }
197 override @property bool isEditConstraint () const pure @nogc { return true; }
201 public class CswLinearConstraint : CswConstraint {
202 protected:
203 CswLinearExpression mExpression;
205 public:
206 override @property string expressionStr () const { return mExpression.toString; }
208 @safe:
209 nothrow:
210 this (CswLinearExpression cle, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
211 super(strength, weight);
212 mExpression = cle;
214 override @property CswLinearExpression expression () pure @nogc { return mExpression; }
215 //protected final void setExpression (CswLinearExpression expr) { return mExpression = expr; }
219 public class CswStayConstraint : CswEditOrStayConstraint {
220 override string toString () const { return "stay"~super.toString(); }
221 @safe:
222 nothrow:
223 this (CswVariable var, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) { super(var, strength, weight); }
224 override @property bool isStayConstraint () const pure @nogc { return true; }
228 class CswLinearEquation : CswLinearConstraint {
229 private enum buildCtor(string args, string body) =
230 `this (`~args~`, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {`~body~`}`;
232 override string toString () const { return super.toString()~" = 0)"; }
233 nothrow:
234 @safe {
235 mixin(buildCtor!("CswLinearExpression cle", q{ super(cle, strength, weight); }));
238 mixin(buildCtor!("CswAbstractVariable clv, CswLinearExpression cle", q{
239 super(cle, strength, weight);
240 mExpression.addVariable(clv, -1.0);
241 }));
243 mixin(buildCtor!("CswAbstractVariable clv, CswNumber val", q{
244 super(new CswLinearExpression(val), strength, weight);
245 mExpression.addVariable(clv, -1.0);
246 }));
248 mixin(buildCtor!("CswLinearExpression cle, CswAbstractVariable clv", q{
249 super(cast(CswLinearExpression)cle.clone(), strength, weight);
250 mExpression.addVariable(clv, -1.0);
251 }));
253 mixin(buildCtor!("CswLinearExpression cle1, CswLinearExpression cle2", q{
254 super(cast(CswLinearExpression)cle1.clone(), strength, weight);
255 mExpression.addExpression(cle2, -1.0);
256 }));
258 mixin(buildCtor!("CswAbstractVariable cle, CswAbstractVariable clv", q{
259 this(new CswLinearExpression(cle), clv, strength, weight);
260 }));
264 class CswLinearInequality : CswLinearConstraint {
265 private enum buildCtor(string args, string opr, string sup, string adder="addVariable") =
266 `this (`~args~`, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {`~
267 sup~
268 `switch (op) {`~
269 ` case Csw.CompOp.GEQ:`~
270 ` mExpression.multiplyMe(-1.0);`~
271 ` mExpression.`~adder~`(`~opr~`);`~
272 ` break;`~
273 ` case Csw.CompOp.LEQ:`~
274 ` mExpression.`~adder~`(`~opr~`, -1.0);`~
275 ` break;`~
276 ` default:`~
277 ` throw new CswErrorInternalError("Invalid operator in CswLinearInequality constructor");`~
278 `}`~
279 `}`;
281 this (CswLinearExpression cle, in CswStrength strength=Csw.Required, CswNumber weight=1.0) @safe nothrow {
282 super(cle, strength, weight);
285 mixin(buildCtor!("CswVariable clv1, Csw.CompOp op, CswVariable clv2",
286 `clv1`,
287 `super(new CswLinearExpression(clv2), strength, weight);`));
289 mixin(buildCtor!("CswVariable clv, Csw.CompOp op, CswNumber val",
290 `clv`,
291 `super(new CswLinearExpression(val), strength, weight);`));
293 mixin(buildCtor!("CswLinearExpression cle1, Csw.CompOp op, CswLinearExpression cle2",
294 `cle1`,
295 `super(cast(CswLinearExpression)cle2.clone(), strength, weight);`,
296 `addExpression`));
298 mixin(buildCtor!("CswAbstractVariable clv, Csw.CompOp op, CswLinearExpression cle",
299 `clv`,
300 `super(cast(CswLinearExpression)cle.clone(), strength, weight);`));
302 mixin(buildCtor!("CswLinearExpression cle, Csw.CompOp op, CswAbstractVariable clv",
303 `clv`,
304 `super(cast(CswLinearExpression)cle.clone(), strength, weight);`));
306 override @property bool isInequality () const @safe pure nothrow @nogc { return true; }
308 public override string toString () const { return super.toString()~" >= 0)"; }
312 // ////////////////////////////////////////////////////////////////////////// //
313 // expressions
314 class CswLinearExpression {
315 private:
316 CswNumber mConstant;
318 struct Term {
319 CswAbstractVariable var;
320 CswNumber num;
323 Term[uint] mTerms; // from CswVariable to CswNumber, key is `vindex`
325 public:
326 /// Create 'semi-valid' zero constant
327 this () @safe nothrow { this(0.0); }
328 /// Create constant
329 this (CswNumber num) @safe nothrow { this(null, 0.0, num); }
330 // / Create variable with multiplier
331 // this (CswAbstractVariable clv, CswNumber multiplier=1.0) @safe nothrow { return this(clv, multiplier, 0.0); }
332 /// Create either variable with multiplier or constant (internal constructor).
333 /// Used in CswEditOrStayConstraint
334 this (CswAbstractVariable clv, CswNumber multiplier=1.0, CswNumber constant=0.0) @safe nothrow {
335 //Csw.gcln("new CswLinearExpression");
336 mConstant = constant;
337 if (clv !is null) mTerms[clv.vindex] = Term(clv, multiplier);
340 /// For use by the clone method
341 protected this (in CswNumber constant, Term[uint] terms) @trusted nothrow {
342 //Csw.gcln("clone CswLinearExpression");
343 mConstant = constant;
344 // '_aaApply2' is not nothrow %-(
345 try {
346 foreach (ref clv; terms.byValue) mTerms[clv.var.vindex] = clv;
347 } catch (Exception) {}
350 /// Clone this expression
351 CswLinearExpression clone () @safe nothrow { return new CswLinearExpression(mConstant, mTerms); }
353 /// Multiply this expression by scalar
354 CswLinearExpression multiplyMe (in CswNumber x) @trusted nothrow @nogc {
355 mConstant *= x;
356 foreach (ref cld; mTerms.byValue) cld.num *= x;
357 return this;
360 final CswLinearExpression times (in CswNumber x) @safe nothrow { return clone().multiplyMe(x); }
362 final CswLinearExpression times (CswLinearExpression expr) {
363 if (isConstant) return expr.times(mConstant);
364 if (!expr.isConstant) {
365 //import csw.errors : CswErrorNonlinearExpression;
366 throw new CswErrorNonlinearExpression("CswLinearExpression times(): expr is not constant");
368 return times(expr.mConstant);
371 final CswLinearExpression plus (CswLinearExpression expr) nothrow { return clone().addExpression(expr, 1.0); }
372 final CswLinearExpression plus (CswVariable var) nothrow { return clone().addVariable(var, 1.0); }
374 final CswLinearExpression minus (CswLinearExpression expr) nothrow { return clone().addExpression(expr, -1.0); }
375 final CswLinearExpression minus (CswVariable var) nothrow { return clone().addVariable(var, -1.0); }
377 CswLinearExpression divide (in CswNumber x) {
378 if (Csw.approx(x, 0.0)) {
379 //import csw.errors : CswErrorNonlinearExpression;
380 throw new CswErrorNonlinearExpression("CswLinearExpression divide(): division by zero");
382 return times(1.0/x);
385 final CswLinearExpression divide (CswLinearExpression expr) {
386 if (!expr.isConstant) {
387 //import csw.errors : CswErrorNonlinearExpression;
388 throw new CswErrorNonlinearExpression("CswLinearExpression divide(): expr is not constant");
390 return divide(expr.mConstant);
393 final CswLinearExpression divFrom (CswLinearExpression expr) {
394 if (!isConstant) {
395 //import csw.errors : CswErrorNonlinearExpression;
396 throw new CswErrorNonlinearExpression("CswLinearExpression divFrom(): division by non-constant");
398 if (Csw.approx(mConstant, 0.0)) {
399 //import csw.errors : CswErrorNonlinearExpression;
400 throw new CswErrorNonlinearExpression("CswLinearExpression divFrom(): division by zero");
402 return expr.divide(mConstant);
405 final CswLinearExpression subtractFrom (CswLinearExpression expr) nothrow { return expr.minus(this); }
407 final CswLinearExpression opBinary(string op) (in CswNumber n) if (op == "*") { return this.times(n); }
408 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "*") { return this.times(expr); }
410 final CswLinearExpression opBinary(string op) (in CswNumber n) if (op == "/") { return this.divide(n); }
411 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "/") { return this.divide(expr); }
413 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "+") { return this.plus(expr); }
414 final CswLinearExpression opBinary(string op) (CswVariable var) if (op == "+") { return this.plus(var); }
416 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "-") { return this.minus(expr); }
417 final CswLinearExpression opBinary(string op) (CswVariable var) if (op == "-") { return this.minus(var); }
419 /// Add n*expr to this expression from another expression expr.
420 /// Notify the solver if a variable is added or deleted from this
421 /// expression.
422 final CswLinearExpression addExpression (CswLinearExpression expr, in CswNumber n=1.0,
423 CswAbstractVariable subject=null, CswTableau solver=null) nothrow
425 incrementConstant(n*expr.constant);
426 // '_aaApply2' is not nothrow
427 try {
428 foreach (ref clv; expr.mTerms.byValue) addVariable(clv.var, clv.num*n, subject, solver);
429 return this;
430 } catch(Exception) {
431 assert(0);
435 /// Add a term c*v to this expression. If the expression already
436 /// contains a term involving v, add c to the existing coefficient.
437 /// If the new coefficient is approximately 0, delete v. Notify the
438 /// solver if v appears or disappears from this expression.
439 final CswLinearExpression addVariable (CswAbstractVariable v, in CswNumber c=1.0,
440 CswAbstractVariable subject=null, CswTableau solver=null) nothrow
442 assert(v !is null);
443 // body largely duplicated below
444 if (auto coeff = v.vindex in mTerms) {
445 CswNumber newCoefficient = coeff.num+c;
446 if (Csw.approx(newCoefficient, 0.0)) {
447 mTerms.remove(v.vindex);
448 if (subject !is null && solver !is null) solver.noteRemovedVariable(v, subject);
449 } else {
450 coeff.num = newCoefficient;
452 } else {
453 if (!Csw.approx(c, 0.0)) {
454 mTerms[v.vindex] = Term(v, c);
455 if (subject !is null && solver !is null) solver.noteAddedVariable(v, subject);
458 return this;
461 final CswLinearExpression setVariable (CswAbstractVariable v, CswNumber c) nothrow {
462 //assert(c != 0.0);
463 assert(v !is null);
464 if (auto tt = v.vindex in mTerms) {
465 tt.num = c;
466 } else {
467 mTerms[v.vindex] = Term(v, c);
469 return this;
472 /// Return a pivotable variable in this expression. (It is an error
473 /// if this expression is constant -- signal ExCLInternalError in
474 /// that case). Return null if no pivotable variables
475 final CswAbstractVariable anyPivotableVariable () {
476 if (isConstant) {
477 //import csw.errors : CswErrorInternalError;
478 throw new CswErrorInternalError("anyPivotableVariable called on a constant");
480 foreach (ref clv; mTerms.byValue) if (clv.var.isPivotable) return clv.var;
481 // No pivotable variables, so just return null, and let the caller error if needed
482 return null;
485 /// Replace var with a symbolic expression expr that is equal to it.
486 /// If a variable has been added to this expression that wasn't there
487 /// before, or if a variable has been dropped from this expression
488 /// because it now has a coefficient of 0, inform the solver.
489 /// PRECONDITIONS:
490 /// var occurs with a non-zero coefficient in this expression.
491 final void substituteOut (CswAbstractVariable var, CswLinearExpression expr, CswAbstractVariable subject,
492 CswTableau solver) nothrow
494 CswNumber multiplier = mTerms[var.vindex].num;
495 mTerms.remove(var.vindex);
496 incrementConstant(multiplier*expr.constant);
497 foreach (ref clv; expr.mTerms.byValue) {
498 immutable coeff = clv.num;
499 if (auto dOldCoeff = clv.var.vindex in mTerms) {
500 immutable oldCoeff = dOldCoeff.num;
501 CswNumber newCoeff = oldCoeff+multiplier*coeff;
502 if (Csw.approx(newCoeff, 0.0)) {
503 mTerms.remove(dOldCoeff.var.vindex);
504 solver.noteRemovedVariable(dOldCoeff.var, subject);
505 } else {
506 dOldCoeff.num = newCoeff;
508 } else {
509 // did not have that variable
510 mTerms[clv.var.vindex] = Term(clv.var, multiplier*coeff);
511 solver.noteAddedVariable(clv.var, subject);
516 /// This linear expression currently represents the equation
517 /// oldSubject=self. Destructively modify it so that it represents
518 /// the equation newSubject=self.
520 /// Precondition: newSubject currently has a nonzero coefficient in
521 /// this expression.
523 /// NOTES
524 /// Suppose this expression is c + a*newSubject + a1*v1 + ... + an*vn.
526 /// Then the current equation is
527 /// oldSubject = c + a*newSubject + a1*v1 + ... + an*vn.
528 /// The new equation will be
529 /// newSubject = -c/a + oldSubject/a - (a1/a)*v1 - ... - (an/a)*vn.
530 /// Note that the term involving newSubject has been dropped.
531 final void changeSubject (CswAbstractVariable aOldSubject, CswAbstractVariable aNewSubject) nothrow {
532 assert(aOldSubject !is null);
533 assert(aOldSubject !is aNewSubject);
534 immutable ns = newSubject(aNewSubject);
535 if (auto cld = aOldSubject.vindex in mTerms) {
536 cld.num = ns;
537 } else {
538 mTerms[aOldSubject.vindex] = Term(aOldSubject, ns);
542 /// This linear expression currently represents the equation self=0. Destructively modify it so
543 /// that subject=self represents an equivalent equation.
545 /// Precondition: subject must be one of the variables in this expression.
546 /// NOTES
547 /// Suppose this expression is
548 /// c + a*subject + a1*v1 + ... + an*vn
549 /// representing
550 /// c + a*subject + a1*v1 + ... + an*vn = 0
551 /// The modified expression will be
552 /// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
553 /// representing
554 /// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
556 /// Note that the term involving subject has been dropped.
557 /// Returns the reciprocal, so changeSubject can use it, too
558 final CswNumber newSubject (CswAbstractVariable subject) nothrow {
559 assert(subject !is null);
560 immutable coeff = mTerms[subject.vindex].num;
561 mTerms.remove(subject.vindex);
562 immutable reciprocal = 1.0/coeff;
563 multiplyMe(-reciprocal);
564 return reciprocal;
567 /// Return the coefficient corresponding to variable var, i.e.,
568 /// the 'ci' corresponding to the 'vi' that var is:
569 /// v1*c1 + v2*c2 + .. + vn*cn + c
570 final CswNumber coefficientFor (CswAbstractVariable var) const @safe nothrow @nogc {
571 assert(var !is null);
572 auto coeff = var.vindex in mTerms;
573 return (coeff !is null ? coeff.num : 0.0);
576 final @property CswNumber constant () const @safe pure nothrow @nogc { return mConstant; }
577 final @property void constant (CswNumber v) @safe nothrow @nogc { mConstant = v; }
579 final void incrementConstant (CswNumber c) @safe nothrow @nogc { mConstant = mConstant+c; }
581 final @property bool isConstant () const @safe pure nothrow @nogc { return (mTerms.length == 0); }
583 static CswLinearExpression plus (CswLinearExpression e1, CswLinearExpression e2) { return e1.plus(e2); }
584 static CswLinearExpression minus (CswLinearExpression e1, CswLinearExpression e2) { return e1.minus(e2); }
585 static CswLinearExpression times (CswLinearExpression e1, CswLinearExpression e2) { return e1.times(e2); }
586 static CswLinearExpression divide (CswLinearExpression e1, CswLinearExpression e2) { return e1.divide(e2); }
588 override string toString () const {
589 import std.conv : to;
590 string s;
591 if (!Csw.approx(mConstant, 0.0) || mTerms.length == 0) return to!string(mConstant);
592 bool first = true;
593 foreach (/*auto*/ clv; mTerms.byValue) {
594 import std.string : format;
595 s ~= format((first ? "%s*%s" : " + %s*%s"), clv.num, clv.var);
596 first = false;
598 return s;
601 // required for parser
602 static CswLinearExpression doMath (dchar op, CswLinearExpression e1, CswLinearExpression e2) {
603 //import csw.errors : CswErrorInvalidMathOp;
604 switch (op) {
605 case '+': return plus(e1, e2);
606 case '-': return minus(e1, e2);
607 case '*': return times(e1, e2);
608 case '/': return divide(e1, e2);
609 default: throw new CswErrorInvalidMathOp("CswLinearExpression doMath(): invalid operation");
615 // ////////////////////////////////////////////////////////////////////////// //
616 // tableau
617 private class CswTableau {
618 protected:
619 struct Col {
620 CswAbstractVariable var;
621 CswAbstractVariable[uint] set;
624 // mColumns is a mapping from variables which occur in expressions to the
625 // set of basic variables whose expressions contain them
626 // i.e., it's a mapping from variables in expressions (a column) to the
627 // set of rows that contain them.
628 Col[uint] mColumns; // from CswAbstractVariable to set of variables, key is vindex
630 struct Row {
631 CswAbstractVariable var;
632 CswLinearExpression expr;
635 // mRows maps basic variables to the expressions for that row in the tableau
636 Row[uint] mRows; // from CswAbstractVariable to CswLinearExpression, key is vindex
638 // collection of basic variables that have infeasible rows (used when reoptimizing)
639 CswAbstractVariable[uint] mInfeasibleRows; // key is vindex
641 // set of rows where the basic variable is external
642 // this was added to the Java/C++/C# versions to reduce time in setExternalVariables()
643 CswAbstractVariable[uint] mExternalRows; // key is vindex
645 // set of external variables which are parametric
646 // this was added to the Java/C++/C# versions to reduce time in setExternalVariables()
647 CswAbstractVariable[uint] mExternalParametricVars; // key is vindex
649 public:
650 /// Constructor is protected, since this only supports an ADT for
651 /// the CswSimplexSolver class.
652 protected this () @safe nothrow @nogc {}
654 /// Variable v has been removed from an expression. If the
655 /// expression is in a tableau the corresponding basic variable is
656 /// subject (or if subject is nil then it's in the objective function).
657 /// Update the column cross-indices.
658 final void noteRemovedVariable (CswAbstractVariable v, CswAbstractVariable subject) nothrow {
659 if (subject !is null) mColumns[v.vindex].set.remove(subject.vindex);
662 /// v has been added to the linear expression for subject
663 /// update column cross indices.
664 final void noteAddedVariable (CswAbstractVariable v, CswAbstractVariable subject) nothrow {
665 if (subject !is null) insertColVar(v, subject);
668 /// Returns information about the tableau's internals.
670 /// Originally from Michael Noth <noth@cs.washington.edu>
672 /// Returns:
673 /// String containing the information.
674 string getInternalInfo () const {
675 import std.string : format;
676 string s = "Tableau Information:\n";
677 s ~= format("rows: %s (= %s constraints)", mRows.length, mRows.length-1);
678 s ~= format("\nColumns: %s", mColumns.length);
679 s ~= format("\nInfeasible rows: %s", mInfeasibleRows.length);
680 s ~= format("\nExternal basic variables: %s", mExternalRows.length);
681 s ~= format("\nExternal parametric variables: %s", mExternalParametricVars.length);
682 return s;
685 override string toString () const {
686 import std.string : format;
687 string s = "Tableau:\n";
688 foreach (/*auto*/ ev; mRows.byValue) s ~= format("%s <==> %s\n", ev.var, ev.expr);
690 s ~= format("\nColumns:\n%s", mColumns);
691 s ~= format("\nInfeasible rows: %s", mInfeasibleRows);
693 s ~= format("\nExternal basic variables: %s", mExternalRows);
694 s ~= format("\nExternal parametric variables: %s", mExternalParametricVars);
696 return s;
699 /// Convenience function to insert a variable into
700 /// the set of rows stored at mColumns[paramVar],
701 /// creating a new set if needed.
702 private final void insertColVar (CswAbstractVariable paramVar, CswAbstractVariable rowvar) nothrow {
703 assert(paramVar !is null);
704 assert(rowvar !is null);
705 if (auto rowset = paramVar.vindex in mColumns) {
706 rowset.set[rowvar.vindex] = rowvar;
707 } else {
708 //CswAbstractVariable[CswAbstractVariable] rs;
709 Col rs;
710 rs.var = paramVar;
711 rs.set[rowvar.vindex] = rowvar;
712 mColumns[paramVar.vindex] = rs;
716 // Add v=expr to the tableau, update column cross indices
717 // v becomes a basic variable
718 // expr is now owned by CswTableau class,
719 // and CswTableau is responsible for deleting it
720 // (also, expr better be allocated on the heap!).
721 protected final void addRow (CswAbstractVariable var, CswLinearExpression expr) nothrow {
722 assert(var !is null);
723 assert(expr !is null);
724 // for each variable in expr, add var to the set of rows which
725 // have that variable in their expression
726 mRows[var.vindex] = Row(var, expr);
727 // FIXME: check correctness!
728 foreach (ref clv; expr.mTerms.byValue) {
729 insertColVar(clv.var, var);
730 if (clv.var.isExternal) mExternalParametricVars[clv.var.vindex] = clv.var;
732 if (var.isExternal) mExternalRows[var.vindex] = var;
735 // Remove v from the tableau -- remove the column cross indices for v
736 // and remove v from every expression in rows in which v occurs
737 protected final void removeColumn (CswAbstractVariable var) nothrow {
738 assert(var !is null);
739 // remove the rows with the variables in varset
740 if (auto rows = var.vindex in mColumns) {
741 mColumns.remove(var.vindex);
742 foreach (ref clv; rows.set.byValue) {
743 auto expr = mRows[clv.vindex].expr;
744 expr.mTerms.remove(var.vindex);
745 //clv.expr.mTerms.remove(var.vindex);
747 } else {
748 //Csw.trdebugfln("Could not find var %s in mColumns", var);
750 if (var.isExternal) {
751 mExternalRows.remove(var.vindex);
752 mExternalParametricVars.remove(var.vindex);
756 // Remove the basic variable v from the tableau row v=expr
757 // Then update column cross indices.
758 protected final CswLinearExpression removeRow (CswAbstractVariable var) nothrow {
759 auto expr = mRows[var.vindex].expr;
760 assert(expr !is null); // just in case
761 // For each variable in this expression, update
762 // the column mapping and remove the variable from the list
763 // of rows it is known to be in.
764 foreach (ref clv; expr.mTerms.byValue) {
765 if (auto varset = clv.var.vindex in mColumns) {
766 varset.set.remove(var.vindex);
769 mInfeasibleRows.remove(var.vindex);
770 if (var.isExternal) mExternalRows.remove(var.vindex);
771 mRows.remove(var.vindex);
772 return expr;
775 // Replace all occurrences of oldVar with expr, and update column cross indices
776 // oldVar should now be a basic variable.
777 protected final void substituteOut (CswAbstractVariable oldVar, CswLinearExpression expr) nothrow {
778 auto varset = mColumns[oldVar.vindex];
779 foreach (/*auto*/ v; varset.set.byValue) {
780 auto row = mRows[v.vindex].expr;
781 row.substituteOut(oldVar, expr, v, this);
782 if (v.isRestricted && row.constant < 0.0) mInfeasibleRows[v.vindex] = v;
784 if (oldVar.isExternal) {
785 mExternalRows[oldVar.vindex] = oldVar;
786 mExternalParametricVars.remove(oldVar.vindex);
788 mColumns.remove(oldVar.vindex);
791 //final @property auto columns () const @safe pure nothrow @nogc { return mColumns; }
792 //final @property auto rows () const @safe pure nothrow @nogc { return mRows; }
794 // Return true if and only if the variable subject is in the columns keys
795 protected final bool columnsHasKey (CswAbstractVariable subject) const nothrow @nogc { return (subject.vindex in mColumns) !is null; }
797 protected final CswLinearExpression rowExpression (CswAbstractVariable v) nothrow @nogc {
798 assert(v !is null);
799 auto res = v.vindex in mRows;
800 return (res ? res.expr : null);
805 // ////////////////////////////////////////////////////////////////////////// //
806 // solver
807 // CswEditInfo is privately-used class
808 // that just wraps a constraint, its positive and negative
809 // error variables, and its prior edit constant.
810 // It is used as values in mEditVarMap, and replaces
811 // the parallel vectors of error variables and previous edit
812 // constants from the Smalltalk version of the code.
813 private class CswEditInfo {
814 private:
815 CswConstraint mCtr;
816 CswSlackVariable mSVEditPlus;
817 CswSlackVariable mSVEditMinus;
818 CswNumber mPrevEditConstant;
819 usize mIndex;
821 public:
822 @safe:
823 nothrow:
824 @nogc:
825 this (CswConstraint cn, CswSlackVariable eplus, CswSlackVariable eminus, CswNumber prevEditConstant, usize i) {
826 mCtr = cn;
827 mSVEditPlus = eplus;
828 mSVEditMinus = eminus;
829 mPrevEditConstant = prevEditConstant;
830 mIndex = i;
833 final:
834 pure:
835 @property usize index () const { return mIndex; }
836 @property CswConstraint constraint () pure { return mCtr; }
837 @property CswSlackVariable editPlus () pure { return mSVEditPlus; }
838 @property CswSlackVariable editMinus () pure { return mSVEditMinus; }
840 @property CswNumber prevEditConstant () const { return mPrevEditConstant; }
841 @property void prevEditConstant (CswNumber v) { mPrevEditConstant = v; }
844 // ////////////////////////////////////////////////////////////////////////// //
845 // main worker class -- cassowary simplex solver
847 class CswSimplexSolver : CswTableau {
848 private:
849 // The array of negative error vars for the stay constraints
850 // (need both positive and negative since they have only non-negative values).
851 CswAbstractVariable[] mStayMinusErrorVars;
853 // The array of positive error vars for the stay constraints
854 // (need both positive and negative since they have only non-negative values).
855 CswAbstractVariable[] mStayPlusErrorVars;
857 // Give error variables for a non-required constraints,
858 // maps to CswSlackVariable-s.
859 // Map CswConstraint to set of CswVariable.
860 struct ErrVar {
861 CswConstraint cst;
862 CswAbstractVariable[uint] vars;
865 ErrVar[uint] mErrorVars; // key is cindex
867 // Return a lookup table giving the marker variable for
868 // each constraints (used when deleting a constraint).
869 // Map CswConstraint to CswVariable.
870 struct MKV {
871 CswConstraint cst;
872 CswAbstractVariable var;
875 MKV[uint] mMarkerVars; // key is cindex
877 CswObjectiveVariable mObjective;
879 // Map edit variables to CswEditInfo-s.
880 // CswEditInfo instances contain all the information for an
881 // edit constraints (the edit plus/minus vars, the index [for old-style
882 // resolve(ArrayList...)] interface), and the previous value.
883 // (CswEditInfo replaces the parallel vectors from the Smalltalk impl.)
884 struct EVM {
885 CswAbstractVariable var;
886 CswEditInfo edit;
889 EVM[uint] mEditVarMap; // key is vindex
891 uint mSlackCounter;
892 uint mArtificialCounter;
893 uint mDummyCounter;
895 CswNumber[2] mResolvePair;
897 CswNumber mEpsilon;
899 bool mOptimizeAutomatically;
900 bool mNeedsSolving;
902 usize[] mStackEdCns; // stack
904 CswVariable[string] mVarMap;
905 string[string] mDefineMap; // TODO: defines with args
907 public:
908 final:
909 /// Constructor initializes the fields, and creaties the objective row.
910 this () @safe nothrow {
911 mResolvePair[0] = 0.0;
912 mResolvePair[1] = 0.0;
914 mObjective = new CswObjectiveVariable("Z");
916 mSlackCounter = 0;
917 mArtificialCounter = 0;
918 mDummyCounter = 0;
919 mEpsilon = 1e-8;
921 mOptimizeAutomatically = true;
922 mNeedsSolving = false;
924 CswLinearExpression e = new CswLinearExpression();
925 mRows[mObjective.vindex] = Row(mObjective, e);
927 mStackEdCns ~= 0;
930 /// Convenience function for creating a linear inequality constraint.
931 CswSimplexSolver addLowerBound (CswAbstractVariable v, CswNumber lower) {
932 CswLinearInequality cn = new CswLinearInequality(v, Csw.CompOp.GEQ, new CswLinearExpression(lower));
933 return addConstraint(cn);
936 /// Convenience function for creating a linear inequality constraint.
937 CswSimplexSolver addUpperBound (CswAbstractVariable v, CswNumber upper) {
938 CswLinearInequality cn = new CswLinearInequality(v, Csw.CompOp.LEQ, new CswLinearExpression(upper));
939 return addConstraint(cn);
942 /// Convenience function for creating a pair of linear inequality constraints.
943 CswSimplexSolver addBounds (CswAbstractVariable v, CswNumber lower, CswNumber upper) {
944 addLowerBound(v, lower);
945 addUpperBound(v, upper);
946 return this;
949 /// Add a constraint to the solver.
951 /// Params:
952 /// cn = The constraint to be added.
953 CswSimplexSolver addConstraint (CswConstraint cn) {
954 CswSlackVariable[2] ePlusEMinus;
955 CswNumber prevEConstant = 0.0;
956 CswLinearExpression expr = newExpression(cn, /* output to: */ ePlusEMinus, prevEConstant);
958 bool cAddedOkDirectly = false;
959 try {
960 cAddedOkDirectly = tryAddingDirectly(expr);
961 if (!cAddedOkDirectly) {
962 // could not add directly
963 addWithArtificialVariable(expr);
965 } catch (CswErrorRequiredFailure rf) {
966 throw rf;
967 // wtf?!
970 mNeedsSolving = true;
971 if (cn.isEditConstraint) {
972 immutable i = mEditVarMap.length;
973 CswEditConstraint cnEdit = cast(CswEditConstraint)cn;
974 CswSlackVariable clvEplus = ePlusEMinus[0];
975 CswSlackVariable clvEminus = ePlusEMinus[1];
976 mEditVarMap[cnEdit.variable.vindex] = EVM(cnEdit.variable, new CswEditInfo(cnEdit, clvEplus, clvEminus, prevEConstant, i));
979 if (mOptimizeAutomatically) {
980 optimize(mObjective);
981 setExternalVariables();
983 return this;
986 CswSimplexSolver addConstraint (string s) { return addConstraint(CswParseConstraint(s, this)); }
988 CswSimplexSolver registerVariable (CswVariable var) nothrow {
989 mVarMap[var.name] = var;
990 return this;
993 CswSimplexSolver registerVariable (string name, CswNumber value) nothrow {
994 mVarMap[name] = new CswVariable(name, value);
995 return this;
998 debug package void dumpVars () const {
999 import std.stdio;
1000 writeln("=== VARS ===");
1001 foreach (/*auto*/ v; mVarMap) writeln(" ", v);
1002 writeln("============");
1005 /// Same as AddConstraint, throws no exceptions.
1007 /// Returns:
1008 /// false if the constraint resulted in an unsolvable system, otherwise true.
1009 bool addConstraintNoException (CswConstraint cn) nothrow {
1010 try {
1011 addConstraint(cn);
1012 return true;
1013 } catch (CswErrorRequiredFailure) {
1014 return false;
1015 } catch (Exception) {
1016 assert(0);
1020 /// Add an edit constraint for a variable with a given strength.
1022 /// Params:
1023 /// v = Variable to add an edit constraint to.
1024 /// strength = Strength of the edit constraint.
1025 CswSimplexSolver addEditVar (CswVariable v, in CswStrength strength=Csw.Strong) {
1026 try {
1027 CswEditConstraint cnEdit = new CswEditConstraint(v, strength);
1028 return addConstraint(cnEdit);
1029 } catch (CswErrorRequiredFailure) {
1030 // should not get this
1031 //import csw.errors : CswErrorInternalError;
1032 throw new CswErrorInternalError("required failure when adding an edit variable");
1036 /// Remove the edit constraint previously added.
1038 /// Params:
1039 /// v = Variable to which the edit constraint was added before.
1040 CswSimplexSolver removeEditVar (CswAbstractVariable v) {
1041 CswEditInfo cei = mEditVarMap[v.vindex].edit;
1042 CswConstraint cn = cei.constraint;
1043 removeConstraint(cn);
1044 return this;
1047 /// Marks the start of an edit session.
1049 /// beginEdit should be called before sending resolve()
1050 /// messages, after adding the appropriate edit variables.
1051 CswSimplexSolver beginEdit () {
1052 assert(mEditVarMap.length > 0, "mEditVarMap.length == 0");
1053 // may later want to do more in here
1054 mInfeasibleRows = mInfeasibleRows.init; //mInfeasibleRows.clear();
1055 resetStayConstants();
1056 mStackEdCns ~= mEditVarMap.length;
1057 return this;
1060 /// Marks the end of an edit session.
1062 /// endEdit should be called after editing has finished for now, it
1063 /// just removes all edit variables.
1064 CswSimplexSolver endEdit () {
1065 assert(mEditVarMap.length > 0, "mEditVarMap.length == 0");
1066 resolve();
1067 mStackEdCns.length = mStackEdCns.length-1; //mStackEdCns.Pop();
1068 int n = cast(int)mStackEdCns[$-1]; //FIXME(64); peek
1069 removeEditVarsTo(n);
1070 // may later want to do more in hore
1071 return this;
1074 /// Eliminates all the edit constraints that were added.
1075 CswSimplexSolver removeAllEditVars (int n) {
1076 return removeEditVarsTo(0);
1079 /// Remove the last added edit vars to leave only
1080 /// a specific number left.
1082 /// Params:
1083 /// n = Number of edit variables to keep.
1084 CswSimplexSolver removeEditVarsTo (int n) {
1085 try {
1086 // using '.keys', 'cause mEditVarMap can be modified inside loop
1087 foreach (/*auto*/ v; mEditVarMap.values) {
1088 //CswEditInfo cei = mEditVarMap[v.var.vindex].edit;
1089 auto cei = v.edit;
1090 if (cei.index >= n) removeEditVar(v.var);
1092 assert(mEditVarMap.length == n, "mEditVarMap.length != n");
1093 return this;
1094 } catch (CswErrorConstraintNotFound) {
1095 // should not get this
1096 //import csw.errors : CswErrorInternalError;
1097 throw new CswErrorInternalError("constraint not found in removeEditVarsTo");
1101 /// Add a stay of the given strength (default to CswStrength#weak)
1102 /// of a variable to the tableau..
1104 /// Params:
1105 /// v = Variable to add the stay constraint to.
1106 CswSimplexSolver addStay (CswVariable v, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) {
1107 CswStayConstraint cn = new CswStayConstraint(v, strength, weight);
1108 return addConstraint(cn);
1111 CswSimplexSolver addStay (string name, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) {
1112 if (auto var = name in mVarMap) {
1113 CswStayConstraint cn = new CswStayConstraint(*var, strength, weight);
1114 return addConstraint(cn);
1115 } else {
1116 //debug { import iv.writer; errwriteln("addStay: can't find variable '", name, "'"); }
1117 throw new CswErrorNoVariable("addStay: can't find variable '"~name~"'");
1121 CswVariable variable (string name) {
1122 if (auto var = name in mVarMap) {
1123 return *var;
1124 } else {
1125 //debug { import iv.writer; errwriteln("addStay: can't find variable '", name, "'"); }
1126 throw new CswErrorNoVariable("solver: can't find variable '"~name~"'");
1129 bool hasVariable (string name) const @safe pure nothrow @nogc { return (name in mVarMap) !is null; }
1131 CswVariable opIndex (string name) { return this.variable(name); }
1132 CswNumber opIndexAssign (CswNumber value, string name) { registerVariable(name, value); return value; }
1134 bool hasDefine (string name) const @safe pure nothrow @nogc { return (name in mDefineMap) !is null; }
1135 string define (string name) @safe { return mDefineMap[name]; }
1136 void setDefine (string name, string value) @safe {
1137 assert(name.length > 0);
1138 if (value.length == 0) {
1139 mDefineMap.remove(name);
1140 } else {
1141 mDefineMap[name] = value;
1144 void removeDefine (string name) @safe { return setDefine(name, null); }
1147 /// Remove a constraint from the tableau.
1148 /// Also remove any error variable associated with it.
1149 CswSimplexSolver removeConstraint (CswConstraint cn) {
1150 mNeedsSolving = true;
1151 resetStayConstants();
1153 CswLinearExpression zRow = rowExpression(mObjective);
1154 auto eVars = cn.cindex in mErrorVars;
1155 if (eVars !is null) {
1156 foreach (/*auto*/ clv; eVars.vars.byValue) {
1157 CswLinearExpression expr = rowExpression(clv);
1158 if (expr is null) {
1159 zRow.addVariable(clv, -cn.weight*cn.strength, mObjective, this);
1160 } else {
1161 // the error variable was in the basis
1162 zRow.addExpression(expr, -cn.weight*cn.strength, mObjective, this);
1168 immutable markerVarsCount = mMarkerVars.length;
1169 CswAbstractVariable marker = mMarkerVars[cn];
1170 mMarkerVars.remove(cn);
1172 if (markerVarsCount == mMarkerVars.length) {
1173 // key was not found
1174 throw new CswErrorConstraintNotFound("removeConstraint: constraint not found");
1177 CswAbstractVariable marker;
1178 if (auto mv = cn.cindex in mMarkerVars) {
1179 marker = mv.var;
1180 mMarkerVars.remove(cn.cindex);
1181 } else {
1182 throw new CswErrorConstraintNotFound("removeConstraint: constraint not found");
1185 if (rowExpression(marker) is null) {
1186 // not in the basis, so need to do some more work
1187 auto col = mColumns[marker.vindex];
1188 CswAbstractVariable exitVar = null;
1189 CswNumber minRatio = 0.0;
1190 foreach (/*auto*/ v; col.set) {
1191 if (v.isRestricted) {
1192 CswLinearExpression expr = rowExpression(v);
1193 CswNumber coeff = expr.coefficientFor(marker);
1194 if (coeff < 0.0) {
1195 CswNumber r = -expr.constant/coeff;
1196 if (exitVar is null || r < minRatio) {
1197 minRatio = r;
1198 exitVar = v;
1204 if (exitVar is null) {
1205 foreach (/*auto*/ v; col.set) {
1206 if (v.isRestricted) {
1207 CswLinearExpression expr = rowExpression(v);
1208 CswNumber coeff = expr.coefficientFor(marker);
1209 CswNumber r = expr.constant/coeff;
1210 if (exitVar is null || r < minRatio) {
1211 minRatio = r;
1212 exitVar = v;
1218 if (exitVar is null) {
1219 // exitVar is still null
1220 if (col.set.length == 0) {
1221 removeColumn(marker);
1222 } else {
1223 // put first element in exitVar
1224 exitVar = col.set.byValue.front;
1228 if (exitVar !is null) pivot(marker, exitVar);
1231 if (rowExpression(marker) !is null) removeRow(marker);
1233 if (eVars !is null) {
1234 foreach (/*auto*/ v; eVars.vars.byValue) {
1235 // FIXME: decide wether to use equals or !=
1236 if (v.vindex != marker.vindex) {
1237 removeColumn(v);
1238 // v = null; // is read-only, cannot be set to null
1243 if (cn.isStayConstraint) {
1244 if (eVars !is null) {
1245 foreach (/*auto*/ i; 0..mStayPlusErrorVars.length) {
1246 eVars.vars.remove(mStayPlusErrorVars[i].vindex);
1247 eVars.vars.remove(mStayMinusErrorVars[i].vindex);
1250 } else if (cn.isEditConstraint) {
1251 assert(eVars !is null, "eVars is null");
1252 CswEditConstraint cnEdit = cast(CswEditConstraint)cn;
1253 CswVariable clv = cnEdit.variable;
1254 CswEditInfo cei = mEditVarMap[clv.vindex].edit;
1255 CswSlackVariable clvEditMinus = cei.editMinus;
1256 removeColumn(clvEditMinus);
1257 mEditVarMap.remove(clv.vindex);
1260 // FIXME: do the remove at top
1261 if (eVars !is null) {
1262 //WTF?
1263 //FIXME: mErrorVars.remove(eVars);
1264 mErrorVars.remove(cn.cindex);
1266 marker = null;
1268 if (mOptimizeAutomatically) {
1269 optimize(mObjective);
1270 setExternalVariables();
1273 return this;
1276 /// Re-solve the current collection of constraints for new values
1277 /// for the constants of the edit variables.
1279 /// Deprecated. Use suggestValue(...) then resolve(). If you must
1280 /// use this, be sure to not use it if you remove an edit variable
1281 /// (or edit constraints) from the middle of a list of edits, and
1282 /// then try to resolve with this function (you'll get the wrong
1283 /// answer, because the indices will be wrong in the CswEditInfo
1284 /// objects).
1285 void resolve (CswNumber[] newEditConstants) {
1286 foreach (ref ev; mEditVarMap.byValue) {
1287 //CswEditInfo cei = mEditVarMap[v];
1288 auto v = ev.var;
1289 auto cei = ev.edit;
1290 immutable i = cei.index;
1291 try {
1292 if (i < newEditConstants.length) suggestValue(v, newEditConstants[i]);
1293 } catch (CswError) {
1294 //import csw.errors : CswErrorInternalError;
1295 throw new CswErrorInternalError("Error during resolve");
1298 resolve();
1301 /// Convenience function for resolve-s of two variables.
1302 void resolve (CswNumber x, CswNumber y) {
1303 mResolvePair[0] = x;
1304 mResolvePair[1] = y;
1305 resolve(mResolvePair);
1308 /// Re-solve the current collection of constraints, given the new
1309 /// values for the edit variables that have already been
1310 /// suggested (see suggestValue() method).
1311 void resolve () {
1312 dualOptimize();
1313 setExternalVariables();
1314 mInfeasibleRows = mInfeasibleRows.init; //mInfeasibleRows.clear();
1315 resetStayConstants();
1318 /// suggest a new value for an edit variable.
1320 /// The variable needs to be added as an edit variable and
1321 /// beginEdit() needs to be called before this is called.
1322 /// The tableau will not be solved completely until after resolve()
1323 /// has been called.
1324 CswSimplexSolver suggestValue (CswAbstractVariable v, CswNumber x) {
1325 if (auto ceiv = v.vindex in mEditVarMap) {
1326 auto cei = ceiv.edit;
1327 immutable i = cei.index;
1328 CswSlackVariable clvEditPlus = cei.editPlus;
1329 CswSlackVariable clvEditMinus = cei.editMinus;
1330 CswNumber delta = x-cei.prevEditConstant;
1331 cei.prevEditConstant = x;
1332 deltaEditConstant(delta, clvEditPlus, clvEditMinus);
1333 return this;
1334 } else {
1335 //debug { import iv.writer; errwriteln("suggestValue for variable ", v.toString(), ", but var is not an edit variable"); }
1336 throw new CswError("suggestValue!");
1340 /// Controls wether optimization and setting of external variables is done
1341 /// automatically or not.
1343 /// By default it is done automatically and solve() never needs
1344 /// to be explicitly called by client code. If `autoSolve` is
1345 /// put to false, then solve() needs to be invoked explicitly
1346 /// before using variables' values.
1347 @property bool autoSolve () const @safe pure nothrow @nogc { return mOptimizeAutomatically; }
1348 @property void autoSolve (bool v) @safe nothrow @nogc { mOptimizeAutomatically = v; }
1350 CswSimplexSolver solve () {
1351 if (mNeedsSolving) {
1352 optimize(mObjective);
1353 setExternalVariables();
1355 return this;
1358 CswSimplexSolver setEditedValue (CswVariable v, CswNumber n) {
1359 if (!containsVariable(v)) {
1360 v.changeValue(n);
1361 return this;
1363 if (!Csw.approx(n, v.value)) {
1364 addEditVar(v);
1365 beginEdit();
1366 try {
1367 suggestValue(v, n);
1368 } catch (CswError) {
1369 // just added it above, so we shouldn't get an error
1370 //import csw.errors : CswErrorInternalError;
1371 throw new CswErrorInternalError("Error in setEditedValue");
1373 endEdit();
1375 return this;
1378 bool containsVariable (CswVariable v) nothrow { return columnsHasKey(v) || (rowExpression(v) !is null); }
1380 CswSimplexSolver addVar (CswVariable v) {
1381 if (!containsVariable(v)) {
1382 try {
1383 addStay(v);
1384 } catch (CswErrorRequiredFailure) {
1385 // cannot have a required failure, since we add w/ weak
1386 //import csw.errors : CswErrorInternalError;
1387 throw new CswErrorInternalError("Error in AddVar -- required failure is impossible");
1390 return this;
1393 /// Returns information about the solver's internals.
1395 /// Originally from Michael Noth <noth@cs.washington.edu>
1397 /// Returns:
1398 /// String containing the information.
1399 override string getInternalInfo () const {
1400 import std.string : format;
1401 string result = super.getInternalInfo();
1402 result ~= "\nSolver info:\n";
1403 result ~= "Stay Error Variables: ";
1404 result ~= "%s".format(mStayPlusErrorVars.length+mStayMinusErrorVars.length);
1405 result ~= " (%s +, ".format(mStayPlusErrorVars.length);
1406 result ~= "%s -)\n".format(mStayMinusErrorVars.length);
1407 result ~= "Edit Variables: %s".format(mEditVarMap.length);
1408 result ~= "\n";
1409 return result;
1412 string getDebugInfo () const {
1413 string result = toString();
1414 result ~= getInternalInfo();
1415 result ~= "\n";
1416 return result;
1419 override string toString () const {
1420 import std.string : format;
1421 string result = super.toString();
1422 result ~= "\nmStayPlusErrorVars: %s".format(mStayPlusErrorVars);
1423 result ~= "\nmStayMinusErrorVars: %s".format(mStayMinusErrorVars);
1424 result ~= "\n";
1425 return result;
1428 //// END PUBLIC INTERFACE ////
1430 // Add the constraint expr=0 to the inequality tableau using an
1431 // artificial variable.
1433 // To do this, create an artificial variable av and add av=expr
1434 // to the inequality tableau, then make av be 0 (raise an exception
1435 // if we can't attain av=0).
1436 protected void addWithArtificialVariable (CswLinearExpression expr) {
1437 CswSlackVariable av = new CswSlackVariable(++mArtificialCounter, "a");
1438 CswObjectiveVariable az = new CswObjectiveVariable("az");
1439 CswLinearExpression azRow = /*(CswLinearExpression)*/ expr.clone();
1441 addRow(az, azRow);
1442 addRow(av, expr);
1444 optimize(az);
1446 CswLinearExpression azTableauRow = rowExpression(az);
1447 if (!Csw.approx(azTableauRow.constant, 0.0)) {
1448 removeRow(az);
1449 removeColumn(av);
1450 throw new CswErrorRequiredFailure("!!!");
1453 // see if av is a basic variable
1454 CswLinearExpression e = rowExpression(av);
1456 if (e !is null) {
1457 // find another variable in this row and pivot,
1458 // so that av becomes parametric
1459 if (e.isConstant) {
1460 // if there isn't another variable in the row
1461 // then the tableau contains the equation av=0 --
1462 // just delete av's row
1463 removeRow(av);
1464 removeRow(az);
1465 return;
1467 CswAbstractVariable entryVar = e.anyPivotableVariable();
1468 pivot(entryVar, av);
1470 assert(rowExpression(av) is null, "rowExpression(av) == null)");
1471 removeColumn(av);
1472 removeRow(az);
1475 // Try to add expr directly to the tableau without creating an
1476 // artificial variable.
1478 // We are trying to add the constraint expr=0 to the appropriate
1479 // tableau.
1481 // Returns:
1482 // True if successful and false if not.
1483 protected bool tryAddingDirectly (CswLinearExpression expr) {
1484 CswAbstractVariable subject = chooseSubject(expr);
1485 if (subject is null) return false;
1486 expr.newSubject(subject);
1487 if (columnsHasKey(subject)) substituteOut(subject, expr);
1488 addRow(subject, expr);
1489 return true; // succesfully added directly
1492 // Try to choose a subject (a variable to become basic) from
1493 // among the current variables in expr.
1495 // We are trying to add the constraint expr=0 to the tableaux.
1496 // If expr constains any unrestricted variables, then we must choose
1497 // an unrestricted variable as the subject. Also if the subject is
1498 // new to the solver, we won't have to do any substitutions, so we
1499 // prefer new variables to ones that are currently noted as parametric.
1500 // If expr contains only restricted variables, if there is a restricted
1501 // variable with a negative coefficient that is new to the solver we can
1502 // make that the subject. Otherwise we can't find a subject, so return nil.
1503 // (In this last case we have to add an artificial variable and use that
1504 // variable as the subject -- this is done outside this method though.)
1505 protected CswAbstractVariable chooseSubject (CswLinearExpression expr) {
1506 CswAbstractVariable subject = null; // the current best subject, if any
1508 bool foundUnrestricted = false;
1509 bool foundNewRestricted = false;
1511 //auto terms = expr.mTerms;
1512 foreach (ref clv; expr.mTerms.byValue) {
1513 //CswNumber c = terms[v];
1514 auto v = clv.var;
1515 immutable c = clv.num;
1516 if (foundUnrestricted) {
1517 if (!v.isRestricted) {
1518 if (!columnsHasKey(v)) return v;
1520 } else {
1521 // we haven't found an restricted variable yet
1522 if (v.isRestricted) {
1523 if (!foundNewRestricted && !v.isDummy && c < 0.0) {
1524 auto col = v.vindex in mColumns;
1525 if (col is null || (col.set.length == 1 && columnsHasKey(mObjective))) {
1526 subject = v;
1527 foundNewRestricted = true;
1530 } else {
1531 subject = v;
1532 foundUnrestricted = true;
1536 if (subject !is null) return subject;
1538 CswNumber coeff = 0.0;
1539 foreach (ref clv; expr.mTerms.byValue) {
1540 //CswNumber c = terms[v];
1541 auto v = clv.var;
1542 immutable c = clv.num;
1543 if (!v.isDummy) return null; // nope, no luck
1544 if (!columnsHasKey(v)) {
1545 subject = v;
1546 coeff = c;
1550 if (!Csw.approx(expr.constant, 0.0)) throw new CswErrorRequiredFailure("!!!");
1551 if (coeff > 0.0) expr.multiplyMe(-1);
1553 return subject;
1556 // Make a new linear Expression representing the constraint cn,
1557 // replacing any basic variables with their defining expressions.
1558 // Normalize if necessary so that the Constant is non-negative.
1559 // If the constraint is non-required give its error variables an
1560 // appropriate weight in the objective function.
1561 protected CswLinearExpression newExpression (CswConstraint cn, out CswSlackVariable[2] ePlusEMinus,
1562 out CswNumber prevEConstant)
1564 CswLinearExpression cnExpr = cn.expression;
1565 CswLinearExpression expr = new CswLinearExpression(cnExpr.constant);
1566 CswSlackVariable slackVar = new CswSlackVariable();
1567 CswDummyVariable dummyVar = new CswDummyVariable();
1568 CswSlackVariable eminus = new CswSlackVariable();
1569 CswSlackVariable eplus = new CswSlackVariable();
1570 //auto cnTerms = cnExpr.terms;
1571 foreach (ref clv; cnExpr.mTerms.byValue) {
1572 //CswNumber c = cnTerms[v];
1573 auto v = clv.var;
1574 immutable c = clv.num;
1575 CswLinearExpression e = rowExpression(v);
1576 if (e is null) expr.addVariable(v, c); else expr.addExpression(e, c);
1578 if (cn.isInequality) {
1579 // cn is an inequality, so Add a slack variable. The original constraint
1580 // is expr>=0, so that the resulting equality is expr-slackVar=0. If cn is
1581 // also non-required Add a negative error variable, giving:
1583 // expr - slackVar = -errorVar
1585 // in other words:
1587 // expr - slackVar + errorVar = 0
1589 // Since both of these variables are newly created we can just Add
1590 // them to the Expression (they can't be basic).
1591 ++mSlackCounter;
1592 slackVar = new CswSlackVariable(mSlackCounter, "s");
1593 expr.setVariable(slackVar, -1);
1594 mMarkerVars[cn.cindex] = MKV(cn, slackVar);
1595 if (!cn.isRequired) {
1596 ++mSlackCounter;
1597 eminus = new CswSlackVariable(mSlackCounter, "em");
1598 expr.setVariable(eminus, 1.0);
1599 CswLinearExpression zRow = rowExpression(mObjective);
1600 zRow.setVariable(eminus, cn.strength*cn.weight);
1601 insertErrorVar(cn, eminus);
1602 noteAddedVariable(eminus, mObjective);
1604 } else {
1605 // cn is an equality
1606 if (cn.isRequired) {
1607 // Add a dummy variable to the Expression to serve as a marker for this constraint.
1608 // The dummy variable is never allowed to enter the basis when pivoting.
1609 ++mDummyCounter;
1610 dummyVar = new CswDummyVariable(mDummyCounter, "d");
1611 expr.setVariable(dummyVar, 1.0);
1612 mMarkerVars[cn.cindex] = MKV(cn, dummyVar);
1613 } else {
1614 // cn is a non-required equality. Add a positive and a negative error
1615 // variable, making the resulting constraint
1616 // expr = eplus - eminus
1617 // in other words:
1618 // expr - eplus + eminus = 0
1619 ++mSlackCounter;
1620 eplus = new CswSlackVariable(mSlackCounter, "ep");
1621 eminus = new CswSlackVariable(mSlackCounter, "em");
1623 expr.setVariable(eplus, -1.0);
1624 expr.setVariable(eminus, 1.0);
1625 mMarkerVars[cn.cindex] = MKV(cn, eplus);
1626 CswLinearExpression zRow = rowExpression(mObjective);
1627 immutable swCoeff = cn.strength*cn.weight;
1628 zRow.setVariable(eplus, swCoeff);
1629 noteAddedVariable(eplus, mObjective);
1630 zRow.setVariable(eminus, swCoeff);
1631 noteAddedVariable(eminus, mObjective);
1632 insertErrorVar(cn, eminus);
1633 insertErrorVar(cn, eplus);
1634 if (cn.isStayConstraint) {
1635 mStayPlusErrorVars ~= eplus;
1636 mStayMinusErrorVars ~= eminus;
1637 } else if (cn.isEditConstraint) {
1638 ePlusEMinus[0] = eplus;
1639 ePlusEMinus[1] = eminus;
1640 prevEConstant = cnExpr.constant;
1644 // the Constant in the Expression should be non-negative. If necessary
1645 // normalize the Expression by multiplying by -1
1646 if (expr.constant < 0) expr.multiplyMe(-1);
1647 return expr;
1650 // Minimize the value of the objective.
1652 // The tableau should already be feasible.
1653 protected void optimize (CswObjectiveVariable zVar) {
1654 CswLinearExpression zRow = rowExpression(zVar);
1655 assert(zRow !is null, "zRow != null");
1656 CswAbstractVariable entryVar = null;
1657 CswAbstractVariable exitVar = null;
1658 for (;;) {
1659 CswNumber objectiveCoeff = 0;
1660 //auto terms = zRow.terms;
1661 // Find the most negative coefficient in the objective function (ignoring
1662 // the non-pivotable dummy variables). If all coefficients are positive
1663 // we're done
1664 foreach (ref clv; zRow.mTerms.byValue) {
1665 //CswNumber c = terms[v];
1666 auto v = clv.var;
1667 immutable c = clv.num;
1668 if (v.isPivotable && c < objectiveCoeff) {
1669 objectiveCoeff = c;
1670 entryVar = v;
1673 if (objectiveCoeff >= -mEpsilon || entryVar is null) return;
1674 // choose which variable to move out of the basis
1675 // Only consider pivotable basic variables
1676 // (i.e. restricted, non-dummy variables)
1677 CswNumber minRatio = CswNumber.max;
1678 auto columnVars = mColumns[entryVar.vindex];
1679 CswNumber r = 0.0;
1680 foreach (/*auto*/ v; columnVars.set) {
1681 if (v.isPivotable) {
1682 CswLinearExpression expr = rowExpression(v);
1683 CswNumber coeff = expr.coefficientFor(entryVar);
1684 if (coeff < 0.0) {
1685 r = -expr.constant/coeff;
1686 // Bland's anti-cycling rule:
1687 // if multiple variables are about the same,
1688 // always pick the lowest via some total
1689 // ordering -- I use their addresses in memory
1690 // if (r < minRatio ||
1691 // (c.approx(r, minRatio) &&
1692 // v.get_pclv() < exitVar.get_pclv()))
1693 if (r < minRatio) {
1694 minRatio = r;
1695 exitVar = v;
1700 // If minRatio is still nil at this point, it means that the
1701 // objective function is unbounded, i.e. it can become
1702 // arbitrarily negative. This should never happen in this
1703 // application.
1704 if (minRatio == CswNumber.max) {
1705 //import csw.errors : CswErrorInternalError;
1706 throw new CswErrorInternalError("Objective function is unbounded in optimize");
1708 pivot(entryVar, exitVar);
1712 // Fix the constants in the equations representing the edit constraints.
1714 // Each of the non-required edits will be represented by an equation
1715 // of the form:
1716 // v = c + eplus - eminus
1717 // where v is the variable with the edit, c is the previous edit value,
1718 // and eplus and eminus are slack variables that hold the error in
1719 // satisfying the edit constraint. We are about to change something,
1720 // and we want to fix the constants in the equations representing
1721 // the edit constraints. If one of eplus and eminus is basic, the other
1722 // must occur only in the expression for that basic error variable.
1723 // (They can't both be basic.) Fix the constant in this expression.
1724 // Otherwise they are both non-basic. Find all of the expressions
1725 // in which they occur, and fix the constants in those. See the
1726 // UIST paper for details.
1727 // (This comment was for ResetEditConstants(), but that is now
1728 // gone since it was part of the screwey vector-based interface
1729 // to resolveing. --02/16/99 gjb)
1730 protected void deltaEditConstant (CswNumber delta, CswAbstractVariable plusErrorVar, CswAbstractVariable minusErrorVar) {
1731 CswLinearExpression exprPlus = rowExpression(plusErrorVar);
1732 if (exprPlus !is null) {
1733 exprPlus.incrementConstant(delta);
1734 if (exprPlus.constant < 0.0) mInfeasibleRows[plusErrorVar.vindex] = plusErrorVar;
1735 return;
1738 CswLinearExpression exprMinus = rowExpression(minusErrorVar);
1739 if (exprMinus !is null) {
1740 exprMinus.incrementConstant(-delta);
1741 if (exprMinus.constant < 0.0) mInfeasibleRows[minusErrorVar.vindex] = minusErrorVar;
1742 return;
1745 auto columnVars = mColumns[minusErrorVar.vindex];
1746 foreach (/*auto*/ basicVar; columnVars.set) {
1747 CswLinearExpression expr = rowExpression(basicVar);
1748 //assert(expr != null, "expr != null");
1749 CswNumber c = expr.coefficientFor(minusErrorVar);
1750 expr.incrementConstant(c*delta);
1751 if (basicVar.isRestricted && expr.constant < 0.0) mInfeasibleRows[basicVar.vindex] = basicVar;
1755 // Re-optimize using the dual simplex algorithm.
1757 // We have set new values for the constants in the edit constraints.
1758 protected void dualOptimize () {
1759 CswLinearExpression zRow = rowExpression(mObjective);
1760 while (mInfeasibleRows.length) {
1761 // get first var
1762 CswAbstractVariable exitVar = mInfeasibleRows.byValue.front;
1763 mInfeasibleRows.remove(exitVar.vindex);
1764 CswAbstractVariable entryVar = null;
1765 CswLinearExpression expr = rowExpression(exitVar);
1766 if (expr !is null) {
1767 if (expr.constant < 0.0) {
1768 CswNumber ratio = CswNumber.max;
1769 CswNumber r;
1770 //auto terms = expr.terms;
1771 foreach (ref clv; expr.mTerms.byValue) {
1772 //CswNumber c = terms[v];
1773 auto v = clv.var;
1774 immutable c = clv.num;
1775 if (c > 0.0 && v.isPivotable) {
1776 CswNumber zc = zRow.coefficientFor(v);
1777 r = zc / c; // FIXME: zc / c or zero, as CswSymbolicWeigth-s
1778 if (r < ratio) {
1779 entryVar = v;
1780 ratio = r;
1784 if (ratio == CswNumber.max) {
1785 //import csw.errors : CswErrorInternalError;
1786 throw new CswErrorInternalError("ratio == nil (Double.MaxValue) in dualOptimize");
1788 pivot(entryVar, exitVar);
1794 // Do a pivot. Move entryVar into the basis and move exitVar
1795 // out of the basis.
1797 // We could for example make entryVar a basic variable and
1798 // make exitVar a parametric variable.
1799 protected void pivot (CswAbstractVariable entryVar, CswAbstractVariable exitVar) {
1800 // the entryVar might be non-pivotable if we're doing a
1801 // removeConstraint -- otherwise it should be a pivotable
1802 // variable -- enforced at call sites, hopefully.
1803 // expr is the Expression for the exit variable (about to leave the basis) --
1804 // so that the old tableau includes the equation:
1805 // exitVar = expr
1806 CswLinearExpression expr = removeRow(exitVar);
1807 // Compute an Expression for the entry variable. Since expr has
1808 // been deleted from the tableau we can destructively modify it to
1809 // build this Expression.
1810 expr.changeSubject(exitVar, entryVar);
1811 substituteOut(entryVar, expr);
1812 addRow(entryVar, expr);
1815 // Fix the constants in the equations representing the stays.
1817 // Each of the non-required stays will be represented by an equation
1818 // of the form
1819 // v = c + eplus - eminus
1820 // where v is the variable with the stay, c is the previous value
1821 // of v, and eplus and eminus are slack variables that hold the error
1822 // in satisfying the stay constraint. We are about to change something,
1823 // and we want to fix the constants in the equations representing the
1824 // stays. If both eplus and eminus are nonbasic they have value 0
1825 // in the current solution, meaning the previous stay was exactly
1826 // satisfied. In this case nothing needs to be changed. Otherwise one
1827 // of them is basic, and the other must occur only in the expression
1828 // for that basic error variable. Reset the constant of this
1829 // expression to 0.
1830 protected void resetStayConstants () {
1831 foreach (immutable i; 0..mStayPlusErrorVars.length) {
1832 CswLinearExpression expr = rowExpression(mStayPlusErrorVars[i]);
1833 if (expr is null) expr = rowExpression(mStayMinusErrorVars[i]);
1834 if (expr !is null) expr.constant = 0.0;
1838 // CswSet the external variables known to this solver to their appropriate values.
1840 // CswSet each external basic variable to its value, and set each external parametric
1841 // variable to 0. (It isn't clear that we will ever have external parametric
1842 // variables -- every external variable should either have a stay on it, or have an
1843 // equation that defines it in terms of other external variables that do have stays.
1844 // For the moment I'll put this in though.) Variables that are internal to the solver
1845 // don't actually store values -- their values are just implicit in the tableau -- so
1846 // we don't need to set them.
1847 protected void setExternalVariables () {
1848 foreach (/*auto*/ v; mExternalParametricVars.byValue) {
1849 if (rowExpression(v) !is null) {
1850 //debug { import iv.writer; errwriteln("Error: variable ", v.toString(), "in mExternalParametricVars is basic"); }
1851 continue;
1853 auto vv = cast(CswVariable)v;
1854 vv.changeValue(0.0);
1856 foreach (/*auto*/ v; mExternalRows.byValue) {
1857 CswLinearExpression expr = rowExpression(v);
1858 auto vv = cast(CswVariable)v;
1859 vv.changeValue(expr.constant);
1861 mNeedsSolving = false;
1864 // Protected convenience function to insert an error variable
1865 // into the mErrorVars set, creating the mapping with Add as necessary.
1866 protected void insertErrorVar (CswConstraint cn, CswAbstractVariable var) {
1867 if (auto cnset = cn.cindex in mErrorVars) {
1868 cnset.vars[var.vindex] = var;
1869 } else {
1870 //CswAbstractVariable[CswAbstractVariable] ev;
1871 ErrVar ev;
1872 ev.cst = cn;
1873 ev.vars[var.vindex] = var;
1874 mErrorVars[cn.cindex] = ev;
1878 public:
1879 @property ref CswVariable[string] varMap () @safe nothrow @nogc { return mVarMap; }
1880 @property void varMap (ref CswVariable[string] v) @safe nothrow @nogc { mVarMap = v; }
1884 // ////////////////////////////////////////////////////////////////////////// //
1885 // variables
1886 class CswAbstractVariable {
1887 private:
1888 static uint mVarIndex; // so we can't have more that 0xffffffff variables for the thread lifetime
1890 @property static uint newVarIndex () @trusted nothrow @nogc {
1891 if (++mVarIndex == 0) assert(0, "too many variables in Cassowary!"); // the thing that should not be
1892 return mVarIndex;
1895 private:
1896 uint vindex;
1898 public:
1899 string name;
1901 public:
1902 @safe:
1903 nothrow:
1904 this () { name = buildIndexedName("v", (vindex = newVarIndex)); }
1905 this (string aname) @nogc { vindex = newVarIndex; name = aname; }
1906 this (uint varnumber, string prefix) { name = buildIndexedName(prefix, (vindex = newVarIndex), varnumber); }
1908 const pure @nogc {
1909 @property bool isDummy () const { return false; }
1910 abstract @property bool isExternal ();
1911 abstract @property bool isPivotable ();
1912 abstract @property bool isRestricted ();
1915 @property static uint count () @nogc { return mVarIndex; }
1917 override string toString () const nothrow { return "["~name~":abstract]"; }
1919 protected:
1920 // 4294967296
1921 static string buildIndexedName (string pfx, uint idx, uint num=uint.max) @trusted nothrow {
1922 char[21] n;
1923 usize pos = n.length;
1924 // secondary index
1925 if (num != uint.max) {
1926 do {
1927 n[--pos] = num%10+'0';
1928 num /= 10;
1929 } while (num != 0);
1930 n[--pos] = '#';
1932 // primary index
1933 do {
1934 n[--pos] = idx%10+'0';
1935 idx /= 10;
1936 } while (idx != 0);
1937 import std.exception : assumeUnique;
1938 auto res = new char[](pfx.length+(n.length-pos));
1939 if (pfx.length) res[0..pfx.length] = pfx[];
1940 res[pfx.length..$] = n[pos..$];
1941 return res.assumeUnique;
1944 template HasStr (string s, T...) {
1945 static if (T.length == 0)
1946 enum HasStr = false;
1947 else static if (T[0] == s)
1948 enum HasStr = true;
1949 else
1950 enum HasStr = HasStr!(s, T[1..$]);
1953 template IFS (bool v, string t="true", string f="false") {
1954 static if (v)
1955 enum IFS = t;
1956 else
1957 enum IFS = f;
1960 template GenTypes (T...) {
1961 private enum dum = HasStr!("dummy", T);
1962 private enum ext = HasStr!("extern", T) || HasStr!("external", T);
1963 private enum piv = HasStr!("pivot", T) || HasStr!("pivotable", T);
1964 private enum res = HasStr!("restricted", T);
1965 enum GenTypes =
1966 "override @property bool isDummy () const @safe pure nothrow @nogc { return "~IFS!(dum)~"; }\n"~
1967 "override @property bool isExternal () const @safe pure nothrow @nogc { return "~IFS!(ext)~"; }\n"~
1968 "override @property bool isPivotable () const @safe pure nothrow @nogc { return "~IFS!(piv)~"; }\n"~
1969 "override @property bool isRestricted () const @safe pure nothrow @nogc { return "~IFS!(res)~"; }\n";
1974 class CswDummyVariable : CswAbstractVariable {
1975 this () @safe nothrow { super(); }
1976 this (string name) @safe nothrow @nogc { super(name); }
1977 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1978 override string toString () const nothrow { return "["~name~":dummy]"; }
1979 mixin(GenTypes!("dummy", "restricted"));
1983 class CswSlackVariable : CswAbstractVariable {
1984 this () @safe nothrow { super(); }
1985 this (string name) @safe nothrow @nogc { super(name); }
1986 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1987 override string toString () const nothrow { return "["~name~":slack]"; }
1988 mixin(GenTypes!("pivotable", "restricted"));
1992 class CswObjectiveVariable : CswAbstractVariable {
1993 this () @safe nothrow { super(); }
1994 this (string name) @safe nothrow @nogc { super(name); }
1995 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1996 override string toString () const nothrow { return "["~name~":obj]"; }
1997 mixin(GenTypes!());
2001 class CswVariable : CswAbstractVariable {
2002 private:
2003 CswNumber mValue;
2005 public:
2006 @safe nothrow {
2007 this () { super(); mValue = 0.0; }
2008 this (CswNumber value) { super(); mValue = value; }
2009 this (string name, CswNumber value=0.0) @nogc { super(name); mValue = value; }
2010 this (uint number, string prefix, CswNumber value=0.0) { super(number, prefix); mValue = value; }
2011 this (ref CswVariable[string] varMap, string name, CswNumber value=0.0) { this(name, value); varMap[name] = this; }
2014 override string toString () const nothrow @trusted/*gdc*/ {
2015 try {
2016 import std.conv : to;
2017 return "["~name~":"~to!string(mValue)~"]";
2018 } catch (Exception) {
2019 return "["~name~":???]";
2023 mixin(GenTypes!("external"));
2025 // Change the value held -- should *not* use this if the variable is
2026 // in a solver -- use addEditVar() and suggestValue() interface instead
2027 @property CswNumber value () const @safe pure nothrow @nogc { return mValue; }
2028 @property void value (CswNumber v) @safe nothrow @nogc { mValue = v; }
2030 // Permit overriding in subclasses in case something needs to be
2031 // done when the value is changed by the solver
2032 // may be called when the value hasn't actually changed -- just
2033 // means the solver is setting the external variable
2034 public void changeValue (CswNumber value) @safe pure nothrow @nogc { mValue = value; }
2036 // construct expressions
2037 mixin(buildOpBin!(`*/`, `CswNumber`));
2038 mixin(buildOpBin!(`+-*/`, `CswLinearExpression`));
2039 mixin(buildOpBin!(`+-`, `CswVariable`));
2041 // convert variable to CswLinearExpression
2042 final T opCast(T : CswLinearExpression) () { return new CswLinearExpression(this); }
2044 private:
2045 template buildOpBinConstraint(string ops) {
2046 static if (ops.length > 1)
2047 enum buildOpBinConstraint = `op == "`~ops[0]~`" || `~buildOpBinConstraint!(ops[1..$]);
2048 else
2049 enum buildOpBinConstraint = `op == "`~ops~`"`;
2052 enum buildOpBin(string ops, string argtype) =
2053 `final CswLinearExpression opBinary(string op) (`~argtype~` n)`~
2054 `if (`~buildOpBinConstraint!ops~`) {`~
2055 ` auto e0 = new CswLinearExpression(this);`~
2056 ` mixin("return e0"~op~"n;");`~
2057 `}`;
2061 // ////////////////////////////////////////////////////////////////////////// //
2062 // parser
2063 private:
2064 debug import std.stdio;
2065 debug(CswParser) import std.stdio;
2066 debug(CswTokenizer) import std.stdio;
2069 // ////////////////////////////////////////////////////////////////////////// //
2070 struct Token {
2071 static immutable string oneChars = "=+-*/()[]<>,;:"; // WARNING! keep in sync with Type enum!
2072 enum Type {
2073 EOF,
2075 Number,
2076 EqEq, GEq, LEq, // multichar tokens
2077 // here starts one-char tokens; must be in sync with oneChars
2078 Eq, Plus, Minus, Times, Divide, LParen, RParen, LBrac, RBrac, Less, Great, Comma, Semicolon, Colon
2081 usize pos; // token position in stream
2082 Type type = Type.EOF;
2083 CswNumber n;
2084 string s;
2086 @property bool isEOF () const @safe pure nothrow @nogc { return (type == Type.EOF); }
2087 @property bool isEOX () const @safe pure nothrow @nogc { return (type == Type.EOF || type == Type.Semicolon); }
2088 @property bool isId () const @safe pure nothrow @nogc { return (type == Type.Id); }
2089 @property bool isNumber () const @safe pure nothrow @nogc { return (type == Type.Number); }
2090 @property bool isPunct () const @safe pure nothrow @nogc { return (type > Type.Number && type <= Type.max); }
2092 string toString () const {
2093 import std.conv : to;
2094 switch (type) {
2095 case Type.EOF: return "{EOF}";
2096 case Type.Id: return "{id:"~s~"}";
2097 case Type.Number: return "{num:"~to!string(n)~"}";
2098 case Type.EqEq: return "==";
2099 case Type.GEq: return ">=";
2100 case Type.LEq: return "<=";
2101 default:
2102 if (type >= Type.Eq && type <= Type.max) return to!string(oneChars[type-Type.Eq]);
2103 return "{invalid}";
2109 void skipBlanks (ref ParserData st) {
2110 import std.uni;
2111 for (;;) {
2112 auto ch = st.getChar();
2113 if (ch == 0) return; // end of stream
2114 if (ch <= 32 || std.uni.isWhite(ch)) continue; // skip this char
2115 // slash-slash or slash-star?
2116 if (ch == '/') {
2117 ch = st.getChar(); // skip first slash
2118 if (ch == '*') {
2119 // multiline comment
2120 for (;;) {
2121 ch = st.getChar();
2122 // star-slash?
2123 if (ch == '*') {
2124 ch = st.getChar();
2125 if (ch == '/') break;
2128 continue;
2130 // slash-slash?
2131 if (ch != '/') {
2132 // alas, unget 'em both
2133 st.ungetChar(ch);
2134 st.ungetChar('/');
2135 return;
2137 ch = '#'; // comment-to-eol
2139 // comment-to-eol?
2140 if (ch == '#') {
2141 do { ch = st.getChar(); } while (ch != 0 && ch != '\n');
2142 continue;
2144 // other non-white and non-comment chars
2145 st.ungetChar(ch);
2146 return;
2151 Token getToken (ref ParserData st) {
2152 static bool isId (dchar ch) {
2153 import std.uni : isAlpha;
2154 return
2155 (ch >= '0' && ch <= '9') ||
2156 (ch >= 'A' && ch <= 'Z') ||
2157 (ch >= 'a' && ch <= 'z') ||
2158 ch == '_' || ch == '.' ||
2159 isAlpha(ch);
2162 dchar ch;
2164 skipBlanks(st);
2165 st.lastTokenPos = st.pos;
2166 ch = st.getChar();
2167 debug(CswTokenizer) writeln("lastTokenPos=", st.lastTokenPos, "; ch=", ch);
2168 if (ch == 0) return Token(st.lastTokenPos, Token.Type.EOF);
2170 // try "{?}="
2171 if (ch == '<' || ch == '>' || ch == '=') {
2172 dchar ch1 = st.getChar();
2173 debug(CswTokenizer) writeln(" ?2char; ch=", ch, "; ch1=", ch1);
2174 if (ch1 == '=') {
2175 Token.Type tt = void;
2176 final switch (ch) {
2177 case '<': tt = Token.Type.LEq; break;
2178 case '>': tt = Token.Type.GEq; break;
2179 case '=': tt = Token.Type.EqEq; break;
2181 return Token(st.lastTokenPos, tt);
2183 // restore char
2184 st.ungetChar(ch1);
2187 // one-char token?
2188 if (ch < 127) {
2189 import std.string : indexOf;
2190 debug(CswTokenizer) writeln(" ?punct; ch=", ch);
2191 auto pp = Token.oneChars.indexOf(cast(char)ch);
2192 if (pp >= 0) return Token(st.lastTokenPos, cast(Token.Type)(Token.Type.Eq+pp));
2195 // number?
2196 if (ch >= '0' && ch <= '9') {
2197 CswNumber n = 0.0;
2198 while (ch >= '0' && ch <= '9') {
2199 n = n*10+ch-'0';
2200 ch = st.getChar();
2202 if (ch == '.') {
2203 CswNumber frc = 0.1;
2204 ch = st.getChar();
2205 while (ch >= '0' && ch <= '9') {
2206 n += (ch-'0')*frc;
2207 frc /= 10.0;
2208 ch = st.getChar();
2211 st.ungetChar(ch);
2212 debug(CswTokenizer) writeln(" number=", n);
2213 return Token(st.lastTokenPos, Token.Type.Number, n);
2216 // id?
2217 if (ch != '.' && isId(ch)) {
2218 import std.array : appender;
2219 auto id = appender!string();
2220 while (isId(ch)) {
2221 id.put(ch);
2222 ch = st.getChar();
2224 st.ungetChar(ch);
2225 debug(CswTokenizer) writeln(" id=", id.data);
2226 return Token(st.lastTokenPos, Token.Type.Id, 0.0, id.data);
2229 throw new CswErrorParser("invalid token");
2230 assert(0);
2234 // ////////////////////////////////////////////////////////////////////////// //
2235 struct Operator {
2236 enum Assoc { None, Left, Right, Unary }
2237 dchar math;
2238 Token.Type ttype = Token.Type.EOF;
2239 Assoc assoc = Assoc.None;
2240 int prio = -666;
2242 string toString () const {
2243 import std.conv : to;
2244 string s = "["~to!string(math)~"]";
2245 final switch (assoc) {
2246 case Assoc.None: s ~= " (none)"; break;
2247 case Assoc.Left: s ~= " (left)"; break;
2248 case Assoc.Right: s ~= " (right)"; break;
2249 case Assoc.Unary: s ~= " (unary)"; break;
2251 s ~= " (pr:"~to!string(prio)~")";
2252 return s;
2257 struct Term {
2258 enum Type { EOF, Number, Var, Expr, Operator }
2260 Type type = Type.EOF;
2261 CswNumber n;
2262 CswVariable v;
2263 CswLinearExpression e;
2264 Operator op = void;
2266 this (CswNumber an) @safe nothrow @nogc { type = Type.Number; n = an; }
2267 this (CswVariable av) @safe nothrow @nogc { type = Type.Var; v = av; }
2268 this (CswLinearExpression ae) @safe nothrow @nogc { type = Type.Expr; e = ae; }
2269 this (in Operator aop) @safe nothrow @nogc { type = Type.Operator; op = aop; }
2271 @property bool isEOF () const @safe pure nothrow @nogc { return (type == Type.EOF); }
2272 @property bool isNumber () const @safe pure nothrow @nogc { return (type == Type.Number); }
2273 @property bool isVar () const @safe pure nothrow @nogc { return (type == Type.Var); }
2274 @property bool isExpr () const @safe pure nothrow @nogc { return (type == Type.Expr); }
2275 @property bool isOperator () const @safe pure nothrow @nogc { return (type == Type.Operator); }
2277 T opCast(T : CswLinearExpression) () {
2278 switch (type) {
2279 case Type.Number: return new CswLinearExpression(n);
2280 case Type.Var: return new CswLinearExpression(v);
2281 case Type.Expr: return e;
2282 default: throw new CswErrorParser(`can't cast term to CswLinearExpression`);
2286 string toString () const {
2287 import std.conv : to;
2288 final switch (type) {
2289 case Type.EOF: return "<EOF>";
2290 case Type.Number: return "{num:"~to!string(n)~"}";
2291 case Type.Var: return "{var:"~v.toString()~"}";
2292 case Type.Expr: return "{expr:"~e.toString()~"}";
2293 case Type.Operator: return "{op:"~op.toString()~"}";
2299 static immutable Operator[/*$*/] operators = [
2300 {'(', Token.Type.LParen, Operator.Assoc.Left, -1},
2301 {')', Token.Type.RParen, Operator.Assoc.None, -1},
2302 //{"**", Token.Type.EOF, Operator.Assoc.Right, 4},
2303 //{"^", Token.Type.EOF, Operator.Assoc.Right, 4},
2304 {'*', Token.Type.Times, Operator.Assoc.Left, 2},
2305 {'/', Token.Type.Divide, Operator.Assoc.Left, 2},
2306 {'+', Token.Type.Plus, Operator.Assoc.Left, 1},
2307 {'-', Token.Type.Minus, Operator.Assoc.Left, 1},
2310 static immutable Operator opUnaryMinus = {'-', Token.Type.Minus, Operator.Assoc.Unary, 3};
2313 // ////////////////////////////////////////////////////////////////////////// //
2314 struct ParserData {
2315 enum ExprMode { Constraint, SimpleMath }
2317 string sstr;
2318 CswSimplexSolver solver;
2319 ExprMode mode = ExprMode.Constraint;
2320 usize pos; // current stream position
2321 usize lastTokenPos;
2322 Token tk;
2323 bool tkValid;
2324 dchar[4] savedCh;
2325 usize savedChCount;
2327 void ungetChar (dchar ch) {
2328 if (savedChCount == savedCh.length) throw new CswErrorParser("too many ungetChar()s");
2329 if (ch != 0) {
2330 import std.utf : codeLength;
2331 usize clen = codeLength!dchar(ch);
2332 if (pos > clen) pos -= clen; else pos = 0;
2333 savedCh[savedChCount++] = ch;
2337 dchar getChar () {
2338 usize clen = void;
2339 dchar ch = void;
2340 if (savedChCount) {
2341 import std.utf : codeLength;
2342 ch = savedCh[--savedChCount];
2343 clen = codeLength!dchar(ch);
2344 } else {
2345 import std.utf : decodeFront;
2346 if (sstr.length == 0) return 0; // 0 means EOF
2347 ch = sstr.decodeFront(clen);
2349 pos += clen;
2350 if (ch == 0) ch = ' ';
2351 return ch;
2354 //FIXME: make this faster!
2355 void prependString (string s) {
2356 if (s.length) {
2357 while (savedChCount) {
2358 import std.conv : to;
2359 sstr = to!string(savedCh[--savedChCount])~sstr;
2361 sstr = s~" "~sstr;
2365 Token nextToken () {
2366 if (tkValid) {
2367 tkValid = false;
2368 return tk;
2370 return getToken(this);
2373 Token peekToken () {
2374 if (!tkValid) {
2375 tk = getToken(this);
2376 tkValid = true;
2378 return tk;
2381 void ungetToken (Token atk) {
2382 if (atk.isEOF) return;
2383 if (tkValid) throw new CswErrorParser("internal ungetToken error");
2384 tkValid = true;
2385 tk = atk;
2390 // ////////////////////////////////////////////////////////////////////////// //
2391 Term term (ref ParserData st) {
2392 again:
2393 auto tk = st.nextToken();
2394 if (tk.isEOX) {
2395 st.ungetToken(tk);
2396 return Term();
2398 if (tk.isNumber) return Term(tk.n);
2399 // id?
2400 if (tk.isId) {
2401 // variable?
2402 if (st.solver.hasVariable(tk.s)) {
2403 debug(CswParser) writeln("Term: var '", tk.s, "'");
2404 auto v = st.solver[tk.s];
2405 if (st.mode == ParserData.ExprMode.Constraint) return Term(v);
2406 return Term(v.value); // just a number
2408 // define?
2409 if (st.solver.hasDefine(tk.s)) {
2410 // insert and reparse
2411 // TODO: limit insertions!
2412 auto val = st.solver.define(tk.s);
2413 debug(CswParser) writeln("Term: define '", tk.s, "' = '", val, "'");
2414 st.prependString(val);
2415 goto again;
2417 debug(CswParser) { errwriteln("var not found: '", tk.s, "'"); }
2418 throw new CswErrorParser("var not found: '"~tk.s~"'");
2420 // operator?
2421 if (tk.isPunct) {
2422 // this can be converted to AA, but...
2423 debug(CswParser) writeln("trying punct: ", tk.type);
2424 foreach (immutable op; operators) {
2425 if (op.ttype == tk.type) {
2426 // got it!
2427 debug(CswParser) writeln(" GOT PUNCT!");
2428 return Term(op);
2433 //debug(CswParser) writeln("rest: '", st.sstr, "'");
2434 debug(CswParser) writeln("tk: '", tk, "'");
2435 st.ungetToken(tk);
2436 return Term(); // assume EOF
2440 // ////////////////////////////////////////////////////////////////////////// //
2441 Term expr (ref ParserData st) {
2442 int privBooster = 0;
2443 Term[256] stack, queue; // this should be enough for everyone, hehe
2444 usize sp, qp; // autoinit
2446 void doOperator (ref Term tk) {
2447 debug(CswParser) writeln("doOperator: ", tk);
2448 assert(tk.isOperator);
2449 if (tk.op.assoc == Operator.Assoc.Unary) {
2450 if (qp < 1) throw new CswErrorParser("invalid expression");
2451 debug(CswParser) writeln("op0: ", queue[qp-1]);
2452 if (tk.op.math == '+') return; // no-op
2453 if (queue[qp-1].isNumber) {
2454 queue[qp-1].n = -queue[qp-1].n;
2455 } else {
2456 auto eres = (cast(CswLinearExpression)queue[qp-1])*(-1.0);
2457 queue[qp-1] = Term(eres);
2459 return;
2461 // for now we has only 2ops, so...
2462 if (qp < 2) throw new CswErrorParser("invalid expression");
2463 debug(CswParser) writeln("op0: ", queue[qp-2]);
2464 debug(CswParser) writeln("op1: ", queue[qp-1]);
2465 // let's do that in this funny way
2466 if (queue[qp-2].isNumber && queue[qp-1].isNumber) {
2467 // both operands are numbers, do a little optimisation here
2468 switch (tk.op.math) {
2469 case '+': queue[qp-2].n += queue[qp-1].n; --qp; return;
2470 case '-': queue[qp-2].n -= queue[qp-1].n; --qp; return;
2471 case '*': queue[qp-2].n *= queue[qp-1].n; --qp; return;
2472 case '/': queue[qp-2].n /= queue[qp-1].n; --qp; return;
2473 default:
2476 // if one of the operans is number (0.0 or 1.0), do a little optimisation too
2477 // check 1st
2478 if (queue[qp-2].isNumber) {
2479 if (queue[qp-2].n == 0.0) {
2480 // annihilate both, replace with zero
2481 if (tk.op.math == '+') { queue[qp-2] = queue[qp-1]; --qp; return; } // no-op
2482 else if (tk.op.math == '-') {
2483 // negate
2484 auto eres = (cast(CswLinearExpression)queue[qp-1])*(-1.0);
2485 queue[qp-2] = Term(eres);
2486 --qp;
2487 return;
2489 else if (tk.op.math == '*') { --qp; return; } // this is 0.0
2490 } else if (queue[qp-2].n == 1.0) {
2491 if (tk.op.math == '*' || tk.op.math == '/') {
2492 // no-op
2493 queue[qp-2] = queue[qp-1];
2494 --qp;
2495 return;
2499 // check 2nd
2500 else if (queue[qp-1].isNumber) {
2501 if (queue[qp-1].n == 0.0) {
2502 if (tk.op.math == '+' || tk.op.math == '-') { --qp; return; } // no-op
2503 else if (tk.op.math == '*') {
2504 // no-op
2505 queue[qp-2] = queue[qp-1];
2506 --qp;
2507 return;
2510 else if (queue[qp-1].n == 1.0) {
2511 // leave only first operand
2512 if (tk.op.math == '*') { --qp; return; } // no-op
2515 // do it the hard way
2516 auto eres = CswLinearExpression.doMath(tk.op.math,
2517 cast(CswLinearExpression)queue[qp-2],
2518 cast(CswLinearExpression)queue[qp-1]);
2519 --qp; // drop the last operand
2520 // and replace the first one
2521 queue[qp-1] = Term(eres);
2524 for (;;) {
2525 auto tk = term(st);
2526 if (tk.isEOF) throw new CswErrorParser("unexpected end of expression");
2527 debug(CswParser) writeln("arg: ", tk);
2529 // odd logic here: don't actually stack the parens: don't need to
2530 if (tk.isOperator) {
2531 if (tk.op.ttype == Token.Type.Plus) continue; // unary plus is actually no-op
2532 if (tk.op.ttype == Token.Type.Minus) {
2533 // unary minus
2534 if (sp > 0 && stack[sp-1].isOperator && stack[sp-1].op.ttype == Token.Type.Minus) {
2535 // two unary minuses gives no-op
2536 --sp;
2537 continue;
2539 if (sp >= stack.length) throw new CswErrorParser("expression too complex");
2540 stack[sp++] = Term(opUnaryMinus);
2541 continue;
2543 // LParen is the only other operator allowed here
2544 if (tk.op.ttype == Token.Type.LParen) {
2545 privBooster += 100; // must be higher than max priority
2546 if (privBooster < 0) throw new CswErrorParser("too many parens"); // booster overflowed, heh
2547 continue;
2549 throw new CswErrorParser("invalid expression");
2551 // numbers, vars and exprs are ok here (actually, there can be no expr)
2552 assert(!tk.isExpr);
2554 // put argument to queue
2555 if (qp >= queue.length) throw new CswErrorParser("expression too complex");
2556 queue[qp++] = tk;
2558 // now process operators
2560 another_operator:
2561 tk = term(st);
2562 debug(CswParser) writeln("opr: ", tk);
2563 if (tk.isEOF) {
2564 //if (privBooster) throw new CswErrorParser("unmatched '('");
2565 debug(CswParser) writeln("EXPR DONE");
2566 break; // out of main loop
2569 if (!tk.isOperator) throw new CswErrorParser("operator expected, got "~tk.toString);
2570 if (tk.op.ttype == Token.type.LParen) throw new CswErrorParser("unexpected '(' (internal error)");
2572 bool isRParen = (tk.op.ttype == Token.type.RParen);
2573 if (tk.op.prio > 0) {
2574 // normal operator
2575 tk.op.prio += privBooster;
2576 } else if (isRParen) {
2577 // RParen
2578 if (privBooster < 100) throw new CswErrorParser("unmatched ')'");
2579 tk.op.prio = privBooster;
2582 // process operator stack
2583 while (sp) {
2584 auto t = &stack[sp-1];
2585 if (t.op.prio <= tk.op.prio) {
2586 // stacked prio is less or equal to current
2587 // stop popping if priorities arent equal or operator on the stack is right-associative
2588 if (t.op.prio != tk.op.prio || t.op.assoc == Operator.Assoc.Right) break;
2590 if (tk.op.assoc == Operator.Assoc.Unary && t.op.assoc != Operator.Assoc.Unary) break; // unaries can apply only unaries
2591 // do current operator
2592 doOperator(*t);
2593 --sp;
2596 if (isRParen) {
2597 privBooster -= 100;
2598 goto another_operator;
2601 if (sp >= stack.length) throw new CswErrorParser("expression too complex");
2602 stack[sp++] = tk;
2603 debug(CswParser) writeln("psh: ", tk);
2606 if (privBooster) throw new CswErrorParser("unmatched '('");
2607 debug(CswParser) writeln("doing operators");
2608 // done, now process all stacked operators
2609 foreach_reverse (ref tk; stack[0..sp]) doOperator(tk);
2610 if (qp != 1) throw new CswErrorParser("invalid expression");
2611 debug(CswParser) writeln("RESULT: ", queue[0]);
2612 return queue[0];
2616 // ////////////////////////////////////////////////////////////////////////// //
2617 // <required|weak|medium|strong[:weight]>
2618 // <w0,w1,w2[:weight]>
2619 // return true if strength was here, current token is ">"
2620 bool parseStrength (ref ParserData st, ref CswStrength str, ref CswNumber weight) {
2621 auto tk = st.peekToken();
2622 // strength?
2623 if (tk.type == Token.Type.LBrac || tk.type == Token.Type.Less) {
2624 tk = st.nextToken(); // read brc
2625 auto end = (tk.type == Token.Type.LBrac ? Token.Type.RBrac : Token.Type.Great);
2626 tk = st.nextToken();
2627 if (tk.type == Token.Type.Id) {
2628 // named
2629 switch (tk.s) {
2630 case "required": str = Csw.Required; break;
2631 case "weak": str = Csw.Weak; break;
2632 case "medium": str = Csw.Medium; break;
2633 case "strong": str = Csw.Strong; break;
2634 default: throw new CswErrorParser("invalid strength: '"~tk.s~"'");
2636 tk = st.nextToken();
2637 } else if (tk.type != end && tk.type != Token.Type.Colon) {
2638 // numeric
2639 CswNumber[3] nn = void;
2640 foreach (immutable idx; 0..nn.length) {
2641 if (!tk.isNumber) throw new CswErrorParser("strength number expected");
2642 nn[idx] = tk.n;
2643 tk = st.nextToken();
2644 if (idx != nn.length-1) {
2645 if (tk.type != Token.Type.Comma) throw new CswErrorParser("comma expected");
2646 tk = st.nextToken();
2649 str = Csw.Strength(nn[0], nn[1], nn[2]);
2651 // parse weight
2652 if (tk.type == Token.Type.Colon) {
2653 tk = st.nextToken();
2654 if (!tk.isNumber) throw new CswErrorParser("weight number expected");
2655 weight = tk.n;
2656 tk = st.nextToken();
2658 // check for closing bracket
2659 if (tk.type != end) throw new CswErrorParser(end == Token.Type.RBrac ? "']' expected" : "'>' expected");
2660 return true;
2661 } else {
2662 return false;
2667 // <required|weak|medium|strong[:weight]> expr eqop expr
2668 // <w0,w1,w2[:weight]> expr eqop expr
2669 // default <required>
2670 CswConstraint constraint (ref ParserData st) {
2671 CswStrength str = Csw.Required; // default strength
2672 CswNumber weight = 1.0; // default weight
2673 parseStrength(st, str, weight);
2674 // left part
2675 auto ex0 = expr(st);
2676 // operator
2677 auto tk = st.nextToken();
2678 if (tk.type == Token.Type.Eq || tk.type == Token.Type.EqEq) {
2679 // equation
2680 auto ex1 = expr(st);
2681 //tk = st.nextToken();
2682 //if (!tk.isEOF) throw new CswErrorParser("invalid constraint expression");
2683 return new CswLinearEquation(cast(CswLinearExpression)ex0, cast(CswLinearExpression)ex1, str, weight);
2684 } else if (tk.type == Token.Type.LEq || tk.type == Token.Type.GEq) {
2685 // inequation
2686 auto cop = (tk.type == Token.Type.LEq ? Csw.CompOp.LEQ : Csw.CompOp.GEQ);
2687 auto ex1 = expr(st);
2688 //tk = st.nextToken();
2689 //if (!tk.isEOF) throw new CswErrorParser("invalid constraint expression");
2690 return new CswLinearInequality(cast(CswLinearExpression)ex0, cop, cast(CswLinearExpression)ex1, str, weight);
2691 } else {
2692 throw new CswErrorParser("invalid constraint expression");
2694 assert(0);
2698 // ////////////////////////////////////////////////////////////////////////// //
2699 public CswConstraint CswParseConstraint (string s, CswSimplexSolver solver) {
2700 try {
2701 auto st = ParserData(s, solver, ParserData.ExprMode.Constraint);
2702 auto res = constraint(st);
2703 if (!st.peekToken().isEOF) throw new CswErrorParser("invalid constraint expression");
2704 return res;
2705 } catch (CswErrorParser e) {
2706 debug { import std.stdio; stderr.writeln("PARSE ERROR IN: '", s, "'"); }
2707 throw e;
2709 assert(0);
2713 // ////////////////////////////////////////////////////////////////////////// //
2714 public CswNumber CswParseSimpleMath (string s, CswSimplexSolver solver) {
2715 try {
2716 auto st = ParserData(s, solver, ParserData.ExprMode.SimpleMath);
2717 auto ex = expr(st);
2718 if (!st.peekToken().isEOF) throw new CswErrorParser("invalid simple math expression");
2719 if (!ex.isNumber) throw new CswErrorParser("invalid simple math expression");
2720 return ex.n;
2721 } catch (CswErrorParser e) {
2722 debug { import std.stdio; stderr.writeln("PARSE ERROR (", e.msg, ") IN: '", s, "'"); }
2723 throw e;
2725 assert(0);
2729 // ////////////////////////////////////////////////////////////////////////// //
2730 public void CswParseScript (string s, CswSimplexSolver solver) {
2732 //TODO: don't duplicate stays (?)
2733 // var[(stay|normal)] varlist ;
2734 // varlist: vardef, varlist | vardef | {empty}
2735 // vardef: [<stay|normal>] varname[=simplemath]
2736 static void processVar (ref ParserData st) {
2737 debug(CswScript) writeln("var-start");
2738 bool isAllStay = false;
2739 auto tk = st.peekToken();
2740 // strength
2741 if (tk.type == Token.Type.LParen) {
2742 // this must be 'stay'
2743 st.nextToken(); // skip LParen
2744 tk = st.nextToken();
2745 if (!tk.isId || (tk.s != "stay" && tk.s != "normal")) throw new CswErrorParser("'stay' expected");
2746 isAllStay = (tk.s == "stay");
2747 tk = st.nextToken();
2748 if (tk.type != Token.Type.RParen) throw new CswErrorParser("')' expected");
2749 tk = st.peekToken();
2751 // varlist
2752 while (!tk.isEOX) {
2753 CswStrength str = Csw.Weak; // default strength
2754 CswNumber weight = 1.0; // default weight
2755 bool isStay = isAllStay;
2756 tk = st.nextToken(); // Id or Less, skipped
2757 // '<stay>'?
2758 if (tk.type == Token.Type.Less) {
2759 tk = st.nextToken(); // Id
2760 if (!tk.isId || (tk.s != "stay" && tk.s != "normal")) throw new CswErrorParser("'stay' expected");
2761 isStay = (tk.s == "stay");
2762 // '['?
2763 if (st.peekToken().type == Token.Type.LBrac) parseStrength(st, str, weight);
2764 tk = st.nextToken(); // Great
2765 if (tk.type != Token.Type.Great) throw new CswErrorParser("'>' expected");
2766 tk = st.nextToken(); // Id
2767 debug(CswScript) writeln(" isStay is ", isStay);
2769 if (!tk.isId) throw new CswErrorParser("identifier expected");
2770 auto varname = tk.s;
2771 CswNumber varvalue = 0.0; // default variable value is zero
2772 debug(CswScript) writeln(" varname is ", varname);
2773 if (st.peekToken().type == Token.Type.Eq) {
2774 // do initializer
2775 st.nextToken(); // skip '='
2776 st.mode = ParserData.ExprMode.SimpleMath;
2777 auto ex = expr(st);
2778 if (!ex.isNumber) throw new CswErrorParser("invalid variable init expression");
2779 varvalue = ex.n;
2781 debug(CswScript) writeln("var '", varname, "' = ", varvalue, " stay=", isStay);
2782 st.solver.registerVariable(varname, varvalue);
2783 if (isStay) st.solver.addStay(varname, str, weight);
2784 tk = st.peekToken();
2785 debug(CswScript) writeln("tk: ", tk, "; isEOX: ", tk.isEOX);
2786 // comma or EOX
2787 if (!tk.isEOX) {
2788 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2789 tk = st.nextToken(); // skip comma
2792 debug(CswScript) writeln("var-end");
2793 //debug(CswScript) st.solver.dumpVars();
2796 // define name = {tokens};
2797 static void processDefine (ref ParserData st) {
2798 debug(CswScript) writeln("define-start");
2799 while (!st.peekToken().isEOX) {
2800 auto tk = st.nextToken(); // Id
2801 if (!tk.isId) throw new CswErrorParser("identifier expected");
2802 auto defname = tk.s;
2803 tk = st.nextToken(); // should be '='
2804 if (tk.type != Token.Type.Eq) throw new CswErrorParser("'=' expected");
2805 // now cheat: remember current string and start skipping tokens
2806 auto saveds = st.sstr; // we'll need this
2807 tk = st.peekToken();
2808 while (!tk.isEOX && tk.type != Token.Type.Comma) {
2809 st.nextToken(); // skip this token
2810 tk = st.peekToken();
2812 // now cut skipped part of the string and strip spaces
2813 import std.string : strip;
2814 saveds = saveds[0..$-st.sstr.length].strip;
2815 while (saveds.length > 0 && (saveds[$-1] == ';' || saveds[$-1] == ',')) saveds = saveds[0..$-1];
2816 if (saveds.length == 0) throw new CswErrorParser("empty defines are not allowed");
2817 debug(CswScript) writeln("name='", defname, "'; value='", saveds, "'");
2818 st.solver.setDefine(defname, saveds);
2819 if (!tk.isEOX) {
2820 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2821 tk = st.nextToken(); // skip comma
2824 debug(CswScript) writeln("define-end");
2827 static void processUndefine (ref ParserData st) {
2828 st.nextToken(); // eat keyword
2829 assert(0);
2832 static void processPrint (ref ParserData st) {
2833 debug(CswScript) writeln("print-start");
2834 while (!st.peekToken().isEOX) {
2835 auto tk = st.nextToken(); // eat Id
2836 if (!tk.isId) throw new CswErrorParser("identifier expected");
2837 if (!st.solver.hasVariable(tk.s)) {
2838 import std.stdio;
2839 writeln("*** UNKNOWN VARIABLE: '", tk.s, "'");
2840 } else {
2841 import std.stdio;
2842 writeln(st.solver[tk.s]);
2844 tk = st.peekToken();
2845 if (!tk.isEOX) {
2846 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2847 st.nextToken(); // skip comma
2850 debug(CswScript) writeln("print-end");
2853 static void processConstraint (ref ParserData st) {
2854 debug(CswScript) writeln("constraint-start");
2855 st.mode = ParserData.ExprMode.Constraint;
2856 auto cs = constraint(st);
2857 if (!st.peekToken().isEOX) throw new CswErrorParser("';' expected");
2858 debug(CswScript) writeln("constraint: ", cs);
2859 st.solver.addConstraint(cs);
2860 debug(CswScript) writeln("constraint-start");
2863 auto st = ParserData(s, solver); // mode is irrelevant here
2864 try {
2865 auto tk = st.nextToken();
2866 while (!tk.isEOF) {
2867 if (!tk.isEOX) {
2868 // check for keywords
2869 if (tk.isId) {
2870 debug(CswScript) writeln("ID: ", tk.s);
2871 switch (tk.s) {
2872 case "var": processVar(st); break;
2873 case "define": processDefine(st); break;
2874 case "undefine": processUndefine(st); break;
2875 case "print": processPrint(st); break;
2876 default: st.ungetToken(tk); processConstraint(st); break;
2878 } else {
2879 st.ungetToken(tk);
2880 processConstraint(st);
2882 if (!st.peekToken().isEOX) throw new CswErrorParser("invalid script expression");
2884 // skip semicolong
2885 while (st.peekToken().type == Token.Type.Semicolon) st.nextToken();
2886 tk = st.nextToken();
2888 } catch (CswErrorParser e) {
2889 debug {
2890 import std.stdio;
2891 stderr.writeln("PARSE ERROR IN SCRIPT: ", e.msg);
2892 stderr.writeln("POSITION: ", st.lastTokenPos);
2894 //writeln(s[0..st.lastTokenPos]);
2895 //writeln(s[0..st.pos]);
2896 throw e;