egra: checkbox cosmetix
[iv.d.git] / cassowary.d
blob8cef9c9bed9bc46498b0c2e4c6661a643b13c365
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, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.cassowary /*is aliced*/;
20 import iv.alice;
23 // ////////////////////////////////////////////////////////////////////////// //
24 alias CswNumber = double;
25 alias CswStrength = CswNumber;
28 // ////////////////////////////////////////////////////////////////////////// //
29 private mixin template CswErrorErrorBody() {
30 @safe pure nothrow this (string msg, string file=__FILE__, usize line=__LINE__, Throwable next=null) {
31 super(msg, file, line, next);
36 class CswError : Exception { mixin CswErrorErrorBody; }
39 private enum error(string name) = `class CswError`~name~` : CswError { mixin CswErrorErrorBody; }`;
41 mixin(error!("ConstraintNotFound"));
42 mixin(error!("InternalError"));
43 mixin(error!("NonlinearExpression"));
44 mixin(error!("NotEnoughStays"));
45 mixin(error!("RequiredFailure"));
46 mixin(error!("TooDifficult"));
47 mixin(error!("Parser"));
48 mixin(error!("NoVariable"));
49 mixin(error!("InvalidMathOp"));
52 // ////////////////////////////////////////////////////////////////////////// //
53 /// The enumerations from CswLinearInequality,
54 /// and `global' functions that we want easy to access
55 abstract class Csw {
56 private import std.traits : isSomeString;
58 final:
59 static:
60 enum CompOp {
61 GEQ = 1,
62 LEQ = 2
65 CswLinearExpression plus (CswLinearExpression e1, CswLinearExpression e2) nothrow { return e1.plus(e2); }
66 CswLinearExpression plus (CswNumber e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).plus(e2); }
67 CswLinearExpression plus (CswVariable e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).plus(e2); }
68 CswLinearExpression plus (CswLinearExpression e1, CswVariable e2) nothrow { return e1.plus(new CswLinearExpression(e2)); }
69 CswLinearExpression plus (CswVariable e1, CswNumber e2) nothrow { return (new CswLinearExpression(e1)).plus(new CswLinearExpression(e2)); }
70 CswLinearExpression plus (CswNumber e1, CswVariable e2) nothrow { return (new CswLinearExpression(e1)).plus(new CswLinearExpression(e2)); }
71 CswLinearExpression minus (CswLinearExpression e1, CswLinearExpression e2) nothrow { return e1.minus(e2); }
72 CswLinearExpression minus (CswNumber e1, CswLinearExpression e2) nothrow { return (new CswLinearExpression(e1)).minus(e2); }
73 CswLinearExpression minus (CswLinearExpression e1, CswNumber e2) nothrow { return e1.minus(new CswLinearExpression(e2)); }
74 CswLinearExpression times (CswLinearExpression e1, CswLinearExpression e2) { return e1.times(e2); }
75 CswLinearExpression times (CswLinearExpression e1, CswVariable e2) { return e1.times(new CswLinearExpression(e2)); }
76 CswLinearExpression times (CswVariable e1, CswLinearExpression e2) { return (new CswLinearExpression(e1)).times(e2); }
77 CswLinearExpression times (CswLinearExpression e1, CswNumber e2) { return e1.times(new CswLinearExpression(e2)); }
78 CswLinearExpression times (CswNumber e1, CswLinearExpression e2) { return (new CswLinearExpression(e1)).times(e2); }
79 CswLinearExpression times (CswNumber n, CswVariable clv) nothrow { return new CswLinearExpression(clv, n); }
80 CswLinearExpression times (CswVariable clv, CswNumber n) nothrow { return new CswLinearExpression(clv, n); }
81 CswLinearExpression divide (CswLinearExpression e1, CswLinearExpression e2) { return e1.divide(e2); }
83 bool approx (CswNumber a, CswNumber b) pure @safe nothrow @nogc {
84 import std.math : abs;
85 enum CswNumber epsilon = 1.0e-8;
86 if (a == 0.0) return (abs(b) < epsilon);
87 if (b == 0.0) return (abs(a) < epsilon);
88 return (abs(a-b) < abs(a)*epsilon);
91 bool approx (CswVariable clv, CswNumber b) pure @safe nothrow @nogc { return approx(clv.value, b); }
92 bool approx (CswNumber a, CswVariable clv) pure @safe nothrow @nogc { return approx(a, clv.value); }
94 CswStrength Strength (string name) @safe nothrow @nogc {
95 switch (name) {
96 case "required": return Csw.Required;
97 case "strong": return Csw.Strong;
98 case "medium": return Csw.Medium;
99 case "weak": return Csw.Weak;
100 default: assert(0, "invalid strength name");
104 private enum SWMult = 1000.0;
105 CswNumber Strength (in CswNumber w1, in CswNumber w2, in CswNumber w3) @safe nothrow @nogc { return w3+w2*SWMult+w1*(SWMult*SWMult); }
107 enum Required = Strength(1000, 1000, 1000);
108 enum Strong = Strength(1, 0, 0);
109 enum Medium = Strength(0, 1, 0);
110 enum Weak = Strength(0, 0, 1);
112 private bool isRequiredStrength (CswStrength str) @safe pure nothrow @nogc { return (str >= Required); }
116 // ////////////////////////////////////////////////////////////////////////// //
117 // constraints
118 class CswConstraint {
119 private:
120 static uint mCstIndex;
122 @property static uint newIndex () @trusted nothrow @nogc {
123 if (++mCstIndex == 0) assert(0, "out of constraint indexes");
124 return mCstIndex;
127 uint cindex;
128 CswStrength mStrength = void;
129 CswNumber mWeight;
131 public:
132 override string toString () const {
133 import std.string : format;
134 // example output: weak:[0,0,1] {1} (23 + -1*[update.height:23]
135 return format("%s {%s} (%s", mStrength, weight, expressionStr);
138 abstract @property string expressionStr () const;
140 @nogc:
141 @safe:
142 nothrow:
143 this (in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
144 cindex = newIndex;
145 mStrength = strength;
146 mWeight = weight;
149 abstract @property CswLinearExpression expression ();
151 pure {
152 @property bool isEditConstraint () const { return false; }
153 @property bool isInequality () const { return false; }
154 @property bool isRequired () const { return Csw.isRequiredStrength(mStrength); }
155 @property bool isStayConstraint () const { return false; }
158 final:
159 @property ref CswStrength strength () { return mStrength; }
160 @property void strength (in CswStrength v) { mStrength = v; }
162 @property CswNumber weight () const pure { return mWeight; }
163 @property void weight (CswNumber v) { mWeight = v; }
167 class CswEditOrStayConstraint : CswConstraint {
168 protected CswVariable mVariable;
169 // cache the expression
170 private CswLinearExpression mExpression;
172 public:
173 // add missing bracket -> see CswConstraint#ToString(...)
174 override string toString () const { return super.toString()~")"; }
175 override @property string expressionStr () const { return mExpression.toString; }
177 @safe:
178 nothrow:
179 this (CswVariable var, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
180 super(strength, weight);
181 mVariable = var;
182 mExpression = new CswLinearExpression(mVariable, -1.0, mVariable.value);
185 @nogc:
186 final @property CswVariable variable () pure { return mVariable; }
187 override @property CswLinearExpression expression () pure { return mExpression; }
191 class CswEditConstraint : CswEditOrStayConstraint {
192 override string toString () const { return "edit"~super.toString(); }
193 @safe:
194 nothrow:
195 this (CswVariable clv, in CswStrength strength=Csw.Required, CswNumber weight=1.0) { super(clv, strength, weight); }
196 override @property bool isEditConstraint () const pure @nogc { return true; }
200 public class CswLinearConstraint : CswConstraint {
201 protected:
202 CswLinearExpression mExpression;
204 public:
205 override @property string expressionStr () const { return mExpression.toString; }
207 @safe:
208 nothrow:
209 this (CswLinearExpression cle, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {
210 super(strength, weight);
211 mExpression = cle;
213 override @property CswLinearExpression expression () pure @nogc { return mExpression; }
214 //protected final void setExpression (CswLinearExpression expr) { return mExpression = expr; }
218 public class CswStayConstraint : CswEditOrStayConstraint {
219 override string toString () const { return "stay"~super.toString(); }
220 @safe:
221 nothrow:
222 this (CswVariable var, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) { super(var, strength, weight); }
223 override @property bool isStayConstraint () const pure @nogc { return true; }
227 class CswLinearEquation : CswLinearConstraint {
228 private enum buildCtor(string args, string body) =
229 `this (`~args~`, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {`~body~`}`;
231 override string toString () const { return super.toString()~" = 0)"; }
232 nothrow:
233 @safe {
234 mixin(buildCtor!("CswLinearExpression cle", q{ super(cle, strength, weight); }));
237 mixin(buildCtor!("CswAbstractVariable clv, CswLinearExpression cle", q{
238 super(cle, strength, weight);
239 mExpression.addVariable(clv, -1.0);
240 }));
242 mixin(buildCtor!("CswAbstractVariable clv, CswNumber val", q{
243 super(new CswLinearExpression(val), strength, weight);
244 mExpression.addVariable(clv, -1.0);
245 }));
247 mixin(buildCtor!("CswLinearExpression cle, CswAbstractVariable clv", q{
248 super(cast(CswLinearExpression)cle.clone(), strength, weight);
249 mExpression.addVariable(clv, -1.0);
250 }));
252 mixin(buildCtor!("CswLinearExpression cle1, CswLinearExpression cle2", q{
253 super(cast(CswLinearExpression)cle1.clone(), strength, weight);
254 mExpression.addExpression(cle2, -1.0);
255 }));
257 mixin(buildCtor!("CswAbstractVariable cle, CswAbstractVariable clv", q{
258 this(new CswLinearExpression(cle), clv, strength, weight);
259 }));
263 class CswLinearInequality : CswLinearConstraint {
264 private enum buildCtor(string args, string opr, string sup, string adder="addVariable") =
265 `this (`~args~`, in CswStrength strength=Csw.Required, CswNumber weight=1.0) {`~
266 sup~
267 `switch (op) {`~
268 ` case Csw.CompOp.GEQ:`~
269 ` mExpression.multiplyMe(-1.0);`~
270 ` mExpression.`~adder~`(`~opr~`);`~
271 ` break;`~
272 ` case Csw.CompOp.LEQ:`~
273 ` mExpression.`~adder~`(`~opr~`, -1.0);`~
274 ` break;`~
275 ` default:`~
276 ` throw new CswErrorInternalError("Invalid operator in CswLinearInequality constructor");`~
277 `}`~
278 `}`;
280 this (CswLinearExpression cle, in CswStrength strength=Csw.Required, CswNumber weight=1.0) @safe nothrow {
281 super(cle, strength, weight);
284 mixin(buildCtor!("CswVariable clv1, Csw.CompOp op, CswVariable clv2",
285 `clv1`,
286 `super(new CswLinearExpression(clv2), strength, weight);`));
288 mixin(buildCtor!("CswVariable clv, Csw.CompOp op, CswNumber val",
289 `clv`,
290 `super(new CswLinearExpression(val), strength, weight);`));
292 mixin(buildCtor!("CswLinearExpression cle1, Csw.CompOp op, CswLinearExpression cle2",
293 `cle1`,
294 `super(cast(CswLinearExpression)cle2.clone(), strength, weight);`,
295 `addExpression`));
297 mixin(buildCtor!("CswAbstractVariable clv, Csw.CompOp op, CswLinearExpression cle",
298 `clv`,
299 `super(cast(CswLinearExpression)cle.clone(), strength, weight);`));
301 mixin(buildCtor!("CswLinearExpression cle, Csw.CompOp op, CswAbstractVariable clv",
302 `clv`,
303 `super(cast(CswLinearExpression)cle.clone(), strength, weight);`));
305 override @property bool isInequality () const @safe pure nothrow @nogc { return true; }
307 public override string toString () const { return super.toString()~" >= 0)"; }
311 // ////////////////////////////////////////////////////////////////////////// //
312 // expressions
313 class CswLinearExpression {
314 private:
315 CswNumber mConstant;
317 struct Term {
318 CswAbstractVariable var;
319 CswNumber num;
322 Term[uint] mTerms; // from CswVariable to CswNumber, key is `vindex`
324 public:
325 /// Create 'semi-valid' zero constant
326 this () @safe nothrow { this(0.0); }
327 /// Create constant
328 this (CswNumber num) @safe nothrow { this(null, 0.0, num); }
329 // / Create variable with multiplier
330 // this (CswAbstractVariable clv, CswNumber multiplier=1.0) @safe nothrow { return this(clv, multiplier, 0.0); }
331 /// Create either variable with multiplier or constant (internal constructor).
332 /// Used in CswEditOrStayConstraint
333 this (CswAbstractVariable clv, CswNumber multiplier=1.0, CswNumber constant=0.0) @safe nothrow {
334 //Csw.gcln("new CswLinearExpression");
335 mConstant = constant;
336 if (clv !is null) mTerms[clv.vindex] = Term(clv, multiplier);
339 /// For use by the clone method
340 protected this (in CswNumber constant, Term[uint] terms) @trusted nothrow {
341 //Csw.gcln("clone CswLinearExpression");
342 mConstant = constant;
343 // '_aaApply2' is not nothrow %-(
344 try {
345 foreach (ref clv; terms.byValue) mTerms[clv.var.vindex] = clv;
346 } catch (Exception) {}
349 /// Clone this expression
350 CswLinearExpression clone () @safe nothrow { return new CswLinearExpression(mConstant, mTerms); }
352 /// Multiply this expression by scalar
353 CswLinearExpression multiplyMe (in CswNumber x) @trusted nothrow @nogc {
354 mConstant *= x;
355 foreach (ref cld; mTerms.byValue) cld.num *= x;
356 return this;
359 final CswLinearExpression times (in CswNumber x) @safe nothrow { return clone().multiplyMe(x); }
361 final CswLinearExpression times (CswLinearExpression expr) {
362 if (isConstant) return expr.times(mConstant);
363 if (!expr.isConstant) {
364 //import csw.errors : CswErrorNonlinearExpression;
365 throw new CswErrorNonlinearExpression("CswLinearExpression times(): expr is not constant");
367 return times(expr.mConstant);
370 final CswLinearExpression plus (CswLinearExpression expr) nothrow { return clone().addExpression(expr, 1.0); }
371 final CswLinearExpression plus (CswVariable var) nothrow { return clone().addVariable(var, 1.0); }
373 final CswLinearExpression minus (CswLinearExpression expr) nothrow { return clone().addExpression(expr, -1.0); }
374 final CswLinearExpression minus (CswVariable var) nothrow { return clone().addVariable(var, -1.0); }
376 CswLinearExpression divide (in CswNumber x) {
377 if (Csw.approx(x, 0.0)) {
378 //import csw.errors : CswErrorNonlinearExpression;
379 throw new CswErrorNonlinearExpression("CswLinearExpression divide(): division by zero");
381 return times(1.0/x);
384 final CswLinearExpression divide (CswLinearExpression expr) {
385 if (!expr.isConstant) {
386 //import csw.errors : CswErrorNonlinearExpression;
387 throw new CswErrorNonlinearExpression("CswLinearExpression divide(): expr is not constant");
389 return divide(expr.mConstant);
392 final CswLinearExpression divFrom (CswLinearExpression expr) {
393 if (!isConstant) {
394 //import csw.errors : CswErrorNonlinearExpression;
395 throw new CswErrorNonlinearExpression("CswLinearExpression divFrom(): division by non-constant");
397 if (Csw.approx(mConstant, 0.0)) {
398 //import csw.errors : CswErrorNonlinearExpression;
399 throw new CswErrorNonlinearExpression("CswLinearExpression divFrom(): division by zero");
401 return expr.divide(mConstant);
404 final CswLinearExpression subtractFrom (CswLinearExpression expr) nothrow { return expr.minus(this); }
406 final CswLinearExpression opBinary(string op) (in CswNumber n) if (op == "*") { return this.times(n); }
407 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "*") { return this.times(expr); }
409 final CswLinearExpression opBinary(string op) (in CswNumber n) if (op == "/") { return this.divide(n); }
410 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "/") { return this.divide(expr); }
412 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "+") { return this.plus(expr); }
413 final CswLinearExpression opBinary(string op) (CswVariable var) if (op == "+") { return this.plus(var); }
415 final CswLinearExpression opBinary(string op) (CswLinearExpression expr) if (op == "-") { return this.minus(expr); }
416 final CswLinearExpression opBinary(string op) (CswVariable var) if (op == "-") { return this.minus(var); }
418 /// Add n*expr to this expression from another expression expr.
419 /// Notify the solver if a variable is added or deleted from this
420 /// expression.
421 final CswLinearExpression addExpression (CswLinearExpression expr, in CswNumber n=1.0,
422 CswAbstractVariable subject=null, CswTableau solver=null) nothrow
424 incrementConstant(n*expr.constant);
425 // '_aaApply2' is not nothrow
426 try {
427 foreach (ref clv; expr.mTerms.byValue) addVariable(clv.var, clv.num*n, subject, solver);
428 return this;
429 } catch(Exception) {
430 assert(0);
434 /// Add a term c*v to this expression. If the expression already
435 /// contains a term involving v, add c to the existing coefficient.
436 /// If the new coefficient is approximately 0, delete v. Notify the
437 /// solver if v appears or disappears from this expression.
438 final CswLinearExpression addVariable (CswAbstractVariable v, in CswNumber c=1.0,
439 CswAbstractVariable subject=null, CswTableau solver=null) nothrow
441 assert(v !is null);
442 // body largely duplicated below
443 if (auto coeff = v.vindex in mTerms) {
444 CswNumber newCoefficient = coeff.num+c;
445 if (Csw.approx(newCoefficient, 0.0)) {
446 mTerms.remove(v.vindex);
447 if (subject !is null && solver !is null) solver.noteRemovedVariable(v, subject);
448 } else {
449 coeff.num = newCoefficient;
451 } else {
452 if (!Csw.approx(c, 0.0)) {
453 mTerms[v.vindex] = Term(v, c);
454 if (subject !is null && solver !is null) solver.noteAddedVariable(v, subject);
457 return this;
460 final CswLinearExpression setVariable (CswAbstractVariable v, CswNumber c) nothrow {
461 //assert(c != 0.0);
462 assert(v !is null);
463 if (auto tt = v.vindex in mTerms) {
464 tt.num = c;
465 } else {
466 mTerms[v.vindex] = Term(v, c);
468 return this;
471 /// Return a pivotable variable in this expression. (It is an error
472 /// if this expression is constant -- signal ExCLInternalError in
473 /// that case). Return null if no pivotable variables
474 final CswAbstractVariable anyPivotableVariable () {
475 if (isConstant) {
476 //import csw.errors : CswErrorInternalError;
477 throw new CswErrorInternalError("anyPivotableVariable called on a constant");
479 foreach (ref clv; mTerms.byValue) if (clv.var.isPivotable) return clv.var;
480 // No pivotable variables, so just return null, and let the caller error if needed
481 return null;
484 /// Replace var with a symbolic expression expr that is equal to it.
485 /// If a variable has been added to this expression that wasn't there
486 /// before, or if a variable has been dropped from this expression
487 /// because it now has a coefficient of 0, inform the solver.
488 /// PRECONDITIONS:
489 /// var occurs with a non-zero coefficient in this expression.
490 final void substituteOut (CswAbstractVariable var, CswLinearExpression expr, CswAbstractVariable subject,
491 CswTableau solver) nothrow
493 CswNumber multiplier = mTerms[var.vindex].num;
494 mTerms.remove(var.vindex);
495 incrementConstant(multiplier*expr.constant);
496 foreach (ref clv; expr.mTerms.byValue) {
497 immutable coeff = clv.num;
498 if (auto dOldCoeff = clv.var.vindex in mTerms) {
499 immutable oldCoeff = dOldCoeff.num;
500 CswNumber newCoeff = oldCoeff+multiplier*coeff;
501 if (Csw.approx(newCoeff, 0.0)) {
502 mTerms.remove(dOldCoeff.var.vindex);
503 solver.noteRemovedVariable(dOldCoeff.var, subject);
504 } else {
505 dOldCoeff.num = newCoeff;
507 } else {
508 // did not have that variable
509 mTerms[clv.var.vindex] = Term(clv.var, multiplier*coeff);
510 solver.noteAddedVariable(clv.var, subject);
515 /// This linear expression currently represents the equation
516 /// oldSubject=self. Destructively modify it so that it represents
517 /// the equation newSubject=self.
519 /// Precondition: newSubject currently has a nonzero coefficient in
520 /// this expression.
522 /// NOTES
523 /// Suppose this expression is c + a*newSubject + a1*v1 + ... + an*vn.
525 /// Then the current equation is
526 /// oldSubject = c + a*newSubject + a1*v1 + ... + an*vn.
527 /// The new equation will be
528 /// newSubject = -c/a + oldSubject/a - (a1/a)*v1 - ... - (an/a)*vn.
529 /// Note that the term involving newSubject has been dropped.
530 final void changeSubject (CswAbstractVariable aOldSubject, CswAbstractVariable aNewSubject) nothrow {
531 assert(aOldSubject !is null);
532 assert(aOldSubject !is aNewSubject);
533 immutable ns = newSubject(aNewSubject);
534 if (auto cld = aOldSubject.vindex in mTerms) {
535 cld.num = ns;
536 } else {
537 mTerms[aOldSubject.vindex] = Term(aOldSubject, ns);
541 /// This linear expression currently represents the equation self=0. Destructively modify it so
542 /// that subject=self represents an equivalent equation.
544 /// Precondition: subject must be one of the variables in this expression.
545 /// NOTES
546 /// Suppose this expression is
547 /// c + a*subject + a1*v1 + ... + an*vn
548 /// representing
549 /// c + a*subject + a1*v1 + ... + an*vn = 0
550 /// The modified expression will be
551 /// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
552 /// representing
553 /// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
555 /// Note that the term involving subject has been dropped.
556 /// Returns the reciprocal, so changeSubject can use it, too
557 final CswNumber newSubject (CswAbstractVariable subject) nothrow {
558 assert(subject !is null);
559 immutable coeff = mTerms[subject.vindex].num;
560 mTerms.remove(subject.vindex);
561 immutable reciprocal = 1.0/coeff;
562 multiplyMe(-reciprocal);
563 return reciprocal;
566 /// Return the coefficient corresponding to variable var, i.e.,
567 /// the 'ci' corresponding to the 'vi' that var is:
568 /// v1*c1 + v2*c2 + .. + vn*cn + c
569 final CswNumber coefficientFor (CswAbstractVariable var) const @safe nothrow @nogc {
570 assert(var !is null);
571 auto coeff = var.vindex in mTerms;
572 return (coeff !is null ? coeff.num : 0.0);
575 final @property CswNumber constant () const @safe pure nothrow @nogc { return mConstant; }
576 final @property void constant (CswNumber v) @safe nothrow @nogc { mConstant = v; }
578 final void incrementConstant (CswNumber c) @safe nothrow @nogc { mConstant = mConstant+c; }
580 final @property bool isConstant () const @safe pure nothrow @nogc { return (mTerms.length == 0); }
582 static CswLinearExpression plus (CswLinearExpression e1, CswLinearExpression e2) { return e1.plus(e2); }
583 static CswLinearExpression minus (CswLinearExpression e1, CswLinearExpression e2) { return e1.minus(e2); }
584 static CswLinearExpression times (CswLinearExpression e1, CswLinearExpression e2) { return e1.times(e2); }
585 static CswLinearExpression divide (CswLinearExpression e1, CswLinearExpression e2) { return e1.divide(e2); }
587 override string toString () const {
588 import std.conv : to;
589 string s;
590 if (!Csw.approx(mConstant, 0.0) || mTerms.length == 0) return to!string(mConstant);
591 bool first = true;
592 foreach (/*auto*/ clv; mTerms.byValue) {
593 import std.string : format;
594 s ~= format((first ? "%s*%s" : " + %s*%s"), clv.num, clv.var);
595 first = false;
597 return s;
600 // required for parser
601 static CswLinearExpression doMath (dchar op, CswLinearExpression e1, CswLinearExpression e2) {
602 //import csw.errors : CswErrorInvalidMathOp;
603 switch (op) {
604 case '+': return plus(e1, e2);
605 case '-': return minus(e1, e2);
606 case '*': return times(e1, e2);
607 case '/': return divide(e1, e2);
608 default: throw new CswErrorInvalidMathOp("CswLinearExpression doMath(): invalid operation");
614 // ////////////////////////////////////////////////////////////////////////// //
615 // tableau
616 private class CswTableau {
617 protected:
618 struct Col {
619 CswAbstractVariable var;
620 CswAbstractVariable[uint] set;
623 // mColumns is a mapping from variables which occur in expressions to the
624 // set of basic variables whose expressions contain them
625 // i.e., it's a mapping from variables in expressions (a column) to the
626 // set of rows that contain them.
627 Col[uint] mColumns; // from CswAbstractVariable to set of variables, key is vindex
629 struct Row {
630 CswAbstractVariable var;
631 CswLinearExpression expr;
634 // mRows maps basic variables to the expressions for that row in the tableau
635 Row[uint] mRows; // from CswAbstractVariable to CswLinearExpression, key is vindex
637 // collection of basic variables that have infeasible rows (used when reoptimizing)
638 CswAbstractVariable[uint] mInfeasibleRows; // key is vindex
640 // set of rows where the basic variable is external
641 // this was added to the Java/C++/C# versions to reduce time in setExternalVariables()
642 CswAbstractVariable[uint] mExternalRows; // key is vindex
644 // set of external variables which are parametric
645 // this was added to the Java/C++/C# versions to reduce time in setExternalVariables()
646 CswAbstractVariable[uint] mExternalParametricVars; // key is vindex
648 public:
649 /// Constructor is protected, since this only supports an ADT for
650 /// the CswSimplexSolver class.
651 protected this () @safe nothrow @nogc {}
653 /// Variable v has been removed from an expression. If the
654 /// expression is in a tableau the corresponding basic variable is
655 /// subject (or if subject is nil then it's in the objective function).
656 /// Update the column cross-indices.
657 final void noteRemovedVariable (CswAbstractVariable v, CswAbstractVariable subject) nothrow {
658 if (subject !is null) mColumns[v.vindex].set.remove(subject.vindex);
661 /// v has been added to the linear expression for subject
662 /// update column cross indices.
663 final void noteAddedVariable (CswAbstractVariable v, CswAbstractVariable subject) nothrow {
664 if (subject !is null) insertColVar(v, subject);
667 /// Returns information about the tableau's internals.
669 /// Originally from Michael Noth <noth@cs.washington.edu>
671 /// Returns:
672 /// String containing the information.
673 string getInternalInfo () const {
674 import std.string : format;
675 string s = "Tableau Information:\n";
676 s ~= format("rows: %s (= %s constraints)", mRows.length, mRows.length-1);
677 s ~= format("\nColumns: %s", mColumns.length);
678 s ~= format("\nInfeasible rows: %s", mInfeasibleRows.length);
679 s ~= format("\nExternal basic variables: %s", mExternalRows.length);
680 s ~= format("\nExternal parametric variables: %s", mExternalParametricVars.length);
681 return s;
684 override string toString () const {
685 import std.string : format;
686 string s = "Tableau:\n";
687 foreach (/*auto*/ ev; mRows.byValue) s ~= format("%s <==> %s\n", ev.var, ev.expr);
689 s ~= format("\nColumns:\n%s", mColumns);
690 s ~= format("\nInfeasible rows: %s", mInfeasibleRows);
692 s ~= format("\nExternal basic variables: %s", mExternalRows);
693 s ~= format("\nExternal parametric variables: %s", mExternalParametricVars);
695 return s;
698 /// Convenience function to insert a variable into
699 /// the set of rows stored at mColumns[paramVar],
700 /// creating a new set if needed.
701 private final void insertColVar (CswAbstractVariable paramVar, CswAbstractVariable rowvar) nothrow {
702 assert(paramVar !is null);
703 assert(rowvar !is null);
704 if (auto rowset = paramVar.vindex in mColumns) {
705 rowset.set[rowvar.vindex] = rowvar;
706 } else {
707 //CswAbstractVariable[CswAbstractVariable] rs;
708 Col rs;
709 rs.var = paramVar;
710 rs.set[rowvar.vindex] = rowvar;
711 mColumns[paramVar.vindex] = rs;
715 // Add v=expr to the tableau, update column cross indices
716 // v becomes a basic variable
717 // expr is now owned by CswTableau class,
718 // and CswTableau is responsible for deleting it
719 // (also, expr better be allocated on the heap!).
720 protected final void addRow (CswAbstractVariable var, CswLinearExpression expr) nothrow {
721 assert(var !is null);
722 assert(expr !is null);
723 // for each variable in expr, add var to the set of rows which
724 // have that variable in their expression
725 mRows[var.vindex] = Row(var, expr);
726 // FIXME: check correctness!
727 foreach (ref clv; expr.mTerms.byValue) {
728 insertColVar(clv.var, var);
729 if (clv.var.isExternal) mExternalParametricVars[clv.var.vindex] = clv.var;
731 if (var.isExternal) mExternalRows[var.vindex] = var;
734 // Remove v from the tableau -- remove the column cross indices for v
735 // and remove v from every expression in rows in which v occurs
736 protected final void removeColumn (CswAbstractVariable var) nothrow {
737 assert(var !is null);
738 // remove the rows with the variables in varset
739 if (auto rows = var.vindex in mColumns) {
740 mColumns.remove(var.vindex);
741 foreach (ref clv; rows.set.byValue) {
742 auto expr = mRows[clv.vindex].expr;
743 expr.mTerms.remove(var.vindex);
744 //clv.expr.mTerms.remove(var.vindex);
746 } else {
747 //Csw.trdebugfln("Could not find var %s in mColumns", var);
749 if (var.isExternal) {
750 mExternalRows.remove(var.vindex);
751 mExternalParametricVars.remove(var.vindex);
755 // Remove the basic variable v from the tableau row v=expr
756 // Then update column cross indices.
757 protected final CswLinearExpression removeRow (CswAbstractVariable var) nothrow {
758 auto expr = mRows[var.vindex].expr;
759 assert(expr !is null); // just in case
760 // For each variable in this expression, update
761 // the column mapping and remove the variable from the list
762 // of rows it is known to be in.
763 foreach (ref clv; expr.mTerms.byValue) {
764 if (auto varset = clv.var.vindex in mColumns) {
765 varset.set.remove(var.vindex);
768 mInfeasibleRows.remove(var.vindex);
769 if (var.isExternal) mExternalRows.remove(var.vindex);
770 mRows.remove(var.vindex);
771 return expr;
774 // Replace all occurrences of oldVar with expr, and update column cross indices
775 // oldVar should now be a basic variable.
776 protected final void substituteOut (CswAbstractVariable oldVar, CswLinearExpression expr) nothrow {
777 auto varset = mColumns[oldVar.vindex];
778 foreach (/*auto*/ v; varset.set.byValue) {
779 auto row = mRows[v.vindex].expr;
780 row.substituteOut(oldVar, expr, v, this);
781 if (v.isRestricted && row.constant < 0.0) mInfeasibleRows[v.vindex] = v;
783 if (oldVar.isExternal) {
784 mExternalRows[oldVar.vindex] = oldVar;
785 mExternalParametricVars.remove(oldVar.vindex);
787 mColumns.remove(oldVar.vindex);
790 //final @property auto columns () const @safe pure nothrow @nogc { return mColumns; }
791 //final @property auto rows () const @safe pure nothrow @nogc { return mRows; }
793 // Return true if and only if the variable subject is in the columns keys
794 protected final bool columnsHasKey (CswAbstractVariable subject) const nothrow @nogc { return (subject.vindex in mColumns) !is null; }
796 protected final CswLinearExpression rowExpression (CswAbstractVariable v) nothrow @nogc {
797 assert(v !is null);
798 auto res = v.vindex in mRows;
799 return (res ? res.expr : null);
804 // ////////////////////////////////////////////////////////////////////////// //
805 // solver
806 // CswEditInfo is privately-used class
807 // that just wraps a constraint, its positive and negative
808 // error variables, and its prior edit constant.
809 // It is used as values in mEditVarMap, and replaces
810 // the parallel vectors of error variables and previous edit
811 // constants from the Smalltalk version of the code.
812 private class CswEditInfo {
813 private:
814 CswConstraint mCtr;
815 CswSlackVariable mSVEditPlus;
816 CswSlackVariable mSVEditMinus;
817 CswNumber mPrevEditConstant;
818 usize mIndex;
820 public:
821 @safe:
822 nothrow:
823 @nogc:
824 this (CswConstraint cn, CswSlackVariable eplus, CswSlackVariable eminus, CswNumber prevEditConstant, usize i) {
825 mCtr = cn;
826 mSVEditPlus = eplus;
827 mSVEditMinus = eminus;
828 mPrevEditConstant = prevEditConstant;
829 mIndex = i;
832 final:
833 pure:
834 @property usize index () const { return mIndex; }
835 @property CswConstraint constraint () pure { return mCtr; }
836 @property CswSlackVariable editPlus () pure { return mSVEditPlus; }
837 @property CswSlackVariable editMinus () pure { return mSVEditMinus; }
839 @property CswNumber prevEditConstant () const { return mPrevEditConstant; }
840 @property void prevEditConstant (CswNumber v) { mPrevEditConstant = v; }
843 // ////////////////////////////////////////////////////////////////////////// //
844 // main worker class -- cassowary simplex solver
846 class CswSimplexSolver : CswTableau {
847 private:
848 // The array of negative error vars for the stay constraints
849 // (need both positive and negative since they have only non-negative values).
850 CswAbstractVariable[] mStayMinusErrorVars;
852 // The array of positive error vars for the stay constraints
853 // (need both positive and negative since they have only non-negative values).
854 CswAbstractVariable[] mStayPlusErrorVars;
856 // Give error variables for a non-required constraints,
857 // maps to CswSlackVariable-s.
858 // Map CswConstraint to set of CswVariable.
859 struct ErrVar {
860 CswConstraint cst;
861 CswAbstractVariable[uint] vars;
864 ErrVar[uint] mErrorVars; // key is cindex
866 // Return a lookup table giving the marker variable for
867 // each constraints (used when deleting a constraint).
868 // Map CswConstraint to CswVariable.
869 struct MKV {
870 CswConstraint cst;
871 CswAbstractVariable var;
874 MKV[uint] mMarkerVars; // key is cindex
876 CswObjectiveVariable mObjective;
878 // Map edit variables to CswEditInfo-s.
879 // CswEditInfo instances contain all the information for an
880 // edit constraints (the edit plus/minus vars, the index [for old-style
881 // resolve(ArrayList...)] interface), and the previous value.
882 // (CswEditInfo replaces the parallel vectors from the Smalltalk impl.)
883 struct EVM {
884 CswAbstractVariable var;
885 CswEditInfo edit;
888 EVM[uint] mEditVarMap; // key is vindex
890 uint mSlackCounter;
891 uint mArtificialCounter;
892 uint mDummyCounter;
894 CswNumber[2] mResolvePair;
896 CswNumber mEpsilon;
898 bool mOptimizeAutomatically;
899 bool mNeedsSolving;
901 usize[] mStackEdCns; // stack
903 CswVariable[string] mVarMap;
904 string[string] mDefineMap; // TODO: defines with args
906 public:
907 final:
908 /// Constructor initializes the fields, and creaties the objective row.
909 this () @safe nothrow {
910 mResolvePair[0] = 0.0;
911 mResolvePair[1] = 0.0;
913 mObjective = new CswObjectiveVariable("Z");
915 mSlackCounter = 0;
916 mArtificialCounter = 0;
917 mDummyCounter = 0;
918 mEpsilon = 1e-8;
920 mOptimizeAutomatically = true;
921 mNeedsSolving = false;
923 CswLinearExpression e = new CswLinearExpression();
924 mRows[mObjective.vindex] = Row(mObjective, e);
926 mStackEdCns ~= 0;
929 /// Convenience function for creating a linear inequality constraint.
930 CswSimplexSolver addLowerBound (CswAbstractVariable v, CswNumber lower) {
931 CswLinearInequality cn = new CswLinearInequality(v, Csw.CompOp.GEQ, new CswLinearExpression(lower));
932 return addConstraint(cn);
935 /// Convenience function for creating a linear inequality constraint.
936 CswSimplexSolver addUpperBound (CswAbstractVariable v, CswNumber upper) {
937 CswLinearInequality cn = new CswLinearInequality(v, Csw.CompOp.LEQ, new CswLinearExpression(upper));
938 return addConstraint(cn);
941 /// Convenience function for creating a pair of linear inequality constraints.
942 CswSimplexSolver addBounds (CswAbstractVariable v, CswNumber lower, CswNumber upper) {
943 addLowerBound(v, lower);
944 addUpperBound(v, upper);
945 return this;
948 /// Add a constraint to the solver.
950 /// Params:
951 /// cn = The constraint to be added.
952 CswSimplexSolver addConstraint (CswConstraint cn) {
953 CswSlackVariable[2] ePlusEMinus;
954 CswNumber prevEConstant = 0.0;
955 CswLinearExpression expr = newExpression(cn, /* output to: */ ePlusEMinus, prevEConstant);
957 bool cAddedOkDirectly = false;
958 try {
959 cAddedOkDirectly = tryAddingDirectly(expr);
960 if (!cAddedOkDirectly) {
961 // could not add directly
962 addWithArtificialVariable(expr);
964 } catch (CswErrorRequiredFailure rf) {
965 throw rf;
966 // wtf?!
969 mNeedsSolving = true;
970 if (cn.isEditConstraint) {
971 immutable i = mEditVarMap.length;
972 CswEditConstraint cnEdit = cast(CswEditConstraint)cn;
973 CswSlackVariable clvEplus = ePlusEMinus[0];
974 CswSlackVariable clvEminus = ePlusEMinus[1];
975 mEditVarMap[cnEdit.variable.vindex] = EVM(cnEdit.variable, new CswEditInfo(cnEdit, clvEplus, clvEminus, prevEConstant, i));
978 if (mOptimizeAutomatically) {
979 optimize(mObjective);
980 setExternalVariables();
982 return this;
985 CswSimplexSolver addConstraint (string s) { return addConstraint(CswParseConstraint(s, this)); }
987 CswSimplexSolver registerVariable (CswVariable var) nothrow {
988 mVarMap[var.name] = var;
989 return this;
992 CswSimplexSolver registerVariable (string name, CswNumber value) nothrow {
993 mVarMap[name] = new CswVariable(name, value);
994 return this;
997 debug package void dumpVars () const {
998 import std.stdio;
999 writeln("=== VARS ===");
1000 foreach (/*auto*/ v; mVarMap) writeln(" ", v);
1001 writeln("============");
1004 /// Same as AddConstraint, throws no exceptions.
1006 /// Returns:
1007 /// false if the constraint resulted in an unsolvable system, otherwise true.
1008 bool addConstraintNoException (CswConstraint cn) nothrow {
1009 try {
1010 addConstraint(cn);
1011 return true;
1012 } catch (CswErrorRequiredFailure) {
1013 return false;
1014 } catch (Exception) {
1015 assert(0);
1019 /// Add an edit constraint for a variable with a given strength.
1021 /// Params:
1022 /// v = Variable to add an edit constraint to.
1023 /// strength = Strength of the edit constraint.
1024 CswSimplexSolver addEditVar (CswVariable v, in CswStrength strength=Csw.Strong) {
1025 try {
1026 CswEditConstraint cnEdit = new CswEditConstraint(v, strength);
1027 return addConstraint(cnEdit);
1028 } catch (CswErrorRequiredFailure) {
1029 // should not get this
1030 //import csw.errors : CswErrorInternalError;
1031 throw new CswErrorInternalError("required failure when adding an edit variable");
1035 /// Remove the edit constraint previously added.
1037 /// Params:
1038 /// v = Variable to which the edit constraint was added before.
1039 CswSimplexSolver removeEditVar (CswAbstractVariable v) {
1040 CswEditInfo cei = mEditVarMap[v.vindex].edit;
1041 CswConstraint cn = cei.constraint;
1042 removeConstraint(cn);
1043 return this;
1046 /// Marks the start of an edit session.
1048 /// beginEdit should be called before sending resolve()
1049 /// messages, after adding the appropriate edit variables.
1050 CswSimplexSolver beginEdit () {
1051 assert(mEditVarMap.length > 0, "mEditVarMap.length == 0");
1052 // may later want to do more in here
1053 mInfeasibleRows = mInfeasibleRows.init; //mInfeasibleRows.clear();
1054 resetStayConstants();
1055 mStackEdCns ~= mEditVarMap.length;
1056 return this;
1059 /// Marks the end of an edit session.
1061 /// endEdit should be called after editing has finished for now, it
1062 /// just removes all edit variables.
1063 CswSimplexSolver endEdit () {
1064 assert(mEditVarMap.length > 0, "mEditVarMap.length == 0");
1065 resolve();
1066 mStackEdCns.length = mStackEdCns.length-1; //mStackEdCns.Pop();
1067 int n = cast(int)mStackEdCns[$-1]; //FIXME(64); peek
1068 removeEditVarsTo(n);
1069 // may later want to do more in hore
1070 return this;
1073 /// Eliminates all the edit constraints that were added.
1074 CswSimplexSolver removeAllEditVars (int n) {
1075 return removeEditVarsTo(0);
1078 /// Remove the last added edit vars to leave only
1079 /// a specific number left.
1081 /// Params:
1082 /// n = Number of edit variables to keep.
1083 CswSimplexSolver removeEditVarsTo (int n) {
1084 try {
1085 // using '.keys', 'cause mEditVarMap can be modified inside loop
1086 foreach (/*auto*/ v; mEditVarMap.values) {
1087 //CswEditInfo cei = mEditVarMap[v.var.vindex].edit;
1088 auto cei = v.edit;
1089 if (cei.index >= n) removeEditVar(v.var);
1091 assert(mEditVarMap.length == n, "mEditVarMap.length != n");
1092 return this;
1093 } catch (CswErrorConstraintNotFound) {
1094 // should not get this
1095 //import csw.errors : CswErrorInternalError;
1096 throw new CswErrorInternalError("constraint not found in removeEditVarsTo");
1100 /// Add a stay of the given strength (default to CswStrength#weak)
1101 /// of a variable to the tableau..
1103 /// Params:
1104 /// v = Variable to add the stay constraint to.
1105 CswSimplexSolver addStay (CswVariable v, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) {
1106 CswStayConstraint cn = new CswStayConstraint(v, strength, weight);
1107 return addConstraint(cn);
1110 CswSimplexSolver addStay (string name, in CswStrength strength=Csw.Weak, CswNumber weight=1.0) {
1111 if (auto var = name in mVarMap) {
1112 CswStayConstraint cn = new CswStayConstraint(*var, strength, weight);
1113 return addConstraint(cn);
1114 } else {
1115 //debug { import iv.writer; errwriteln("addStay: can't find variable '", name, "'"); }
1116 throw new CswErrorNoVariable("addStay: can't find variable '"~name~"'");
1120 CswVariable variable (string name) {
1121 if (auto var = name in mVarMap) {
1122 return *var;
1123 } else {
1124 //debug { import iv.writer; errwriteln("addStay: can't find variable '", name, "'"); }
1125 throw new CswErrorNoVariable("solver: can't find variable '"~name~"'");
1128 bool hasVariable (string name) const @safe pure nothrow @nogc { return (name in mVarMap) !is null; }
1130 CswVariable opIndex (string name) { return this.variable(name); }
1131 CswNumber opIndexAssign (CswNumber value, string name) { registerVariable(name, value); return value; }
1133 bool hasDefine (string name) const @safe pure nothrow @nogc { return (name in mDefineMap) !is null; }
1134 string define (string name) @safe { return mDefineMap[name]; }
1135 void setDefine (string name, string value) @safe {
1136 assert(name.length > 0);
1137 if (value.length == 0) {
1138 mDefineMap.remove(name);
1139 } else {
1140 mDefineMap[name] = value;
1143 void removeDefine (string name) @safe { return setDefine(name, null); }
1146 /// Remove a constraint from the tableau.
1147 /// Also remove any error variable associated with it.
1148 CswSimplexSolver removeConstraint (CswConstraint cn) {
1149 mNeedsSolving = true;
1150 resetStayConstants();
1152 CswLinearExpression zRow = rowExpression(mObjective);
1153 auto eVars = cn.cindex in mErrorVars;
1154 if (eVars !is null) {
1155 foreach (/*auto*/ clv; eVars.vars.byValue) {
1156 CswLinearExpression expr = rowExpression(clv);
1157 if (expr is null) {
1158 zRow.addVariable(clv, -cn.weight*cn.strength, mObjective, this);
1159 } else {
1160 // the error variable was in the basis
1161 zRow.addExpression(expr, -cn.weight*cn.strength, mObjective, this);
1167 immutable markerVarsCount = mMarkerVars.length;
1168 CswAbstractVariable marker = mMarkerVars[cn];
1169 mMarkerVars.remove(cn);
1171 if (markerVarsCount == mMarkerVars.length) {
1172 // key was not found
1173 throw new CswErrorConstraintNotFound("removeConstraint: constraint not found");
1176 CswAbstractVariable marker;
1177 if (auto mv = cn.cindex in mMarkerVars) {
1178 marker = mv.var;
1179 mMarkerVars.remove(cn.cindex);
1180 } else {
1181 throw new CswErrorConstraintNotFound("removeConstraint: constraint not found");
1184 if (rowExpression(marker) is null) {
1185 // not in the basis, so need to do some more work
1186 auto col = mColumns[marker.vindex];
1187 CswAbstractVariable exitVar = null;
1188 CswNumber minRatio = 0.0;
1189 foreach (/*auto*/ v; col.set) {
1190 if (v.isRestricted) {
1191 CswLinearExpression expr = rowExpression(v);
1192 CswNumber coeff = expr.coefficientFor(marker);
1193 if (coeff < 0.0) {
1194 CswNumber r = -expr.constant/coeff;
1195 if (exitVar is null || r < minRatio) {
1196 minRatio = r;
1197 exitVar = v;
1203 if (exitVar is null) {
1204 foreach (/*auto*/ v; col.set) {
1205 if (v.isRestricted) {
1206 CswLinearExpression expr = rowExpression(v);
1207 CswNumber coeff = expr.coefficientFor(marker);
1208 CswNumber r = expr.constant/coeff;
1209 if (exitVar is null || r < minRatio) {
1210 minRatio = r;
1211 exitVar = v;
1217 if (exitVar is null) {
1218 // exitVar is still null
1219 if (col.set.length == 0) {
1220 removeColumn(marker);
1221 } else {
1222 // put first element in exitVar
1223 exitVar = col.set.byValue.front;
1227 if (exitVar !is null) pivot(marker, exitVar);
1230 if (rowExpression(marker) !is null) removeRow(marker);
1232 if (eVars !is null) {
1233 foreach (/*auto*/ v; eVars.vars.byValue) {
1234 // FIXME: decide wether to use equals or !=
1235 if (v.vindex != marker.vindex) {
1236 removeColumn(v);
1237 // v = null; // is read-only, cannot be set to null
1242 if (cn.isStayConstraint) {
1243 if (eVars !is null) {
1244 foreach (/*auto*/ i; 0..mStayPlusErrorVars.length) {
1245 eVars.vars.remove(mStayPlusErrorVars[i].vindex);
1246 eVars.vars.remove(mStayMinusErrorVars[i].vindex);
1249 } else if (cn.isEditConstraint) {
1250 assert(eVars !is null, "eVars is null");
1251 CswEditConstraint cnEdit = cast(CswEditConstraint)cn;
1252 CswVariable clv = cnEdit.variable;
1253 CswEditInfo cei = mEditVarMap[clv.vindex].edit;
1254 CswSlackVariable clvEditMinus = cei.editMinus;
1255 removeColumn(clvEditMinus);
1256 mEditVarMap.remove(clv.vindex);
1259 // FIXME: do the remove at top
1260 if (eVars !is null) {
1261 //WTF?
1262 //FIXME: mErrorVars.remove(eVars);
1263 mErrorVars.remove(cn.cindex);
1265 marker = null;
1267 if (mOptimizeAutomatically) {
1268 optimize(mObjective);
1269 setExternalVariables();
1272 return this;
1275 /// Re-solve the current collection of constraints for new values
1276 /// for the constants of the edit variables.
1278 /// Deprecated. Use suggestValue(...) then resolve(). If you must
1279 /// use this, be sure to not use it if you remove an edit variable
1280 /// (or edit constraints) from the middle of a list of edits, and
1281 /// then try to resolve with this function (you'll get the wrong
1282 /// answer, because the indices will be wrong in the CswEditInfo
1283 /// objects).
1284 void resolve (CswNumber[] newEditConstants) {
1285 foreach (ref ev; mEditVarMap.byValue) {
1286 //CswEditInfo cei = mEditVarMap[v];
1287 auto v = ev.var;
1288 auto cei = ev.edit;
1289 immutable i = cei.index;
1290 try {
1291 if (i < newEditConstants.length) suggestValue(v, newEditConstants[i]);
1292 } catch (CswError) {
1293 //import csw.errors : CswErrorInternalError;
1294 throw new CswErrorInternalError("Error during resolve");
1297 resolve();
1300 /// Convenience function for resolve-s of two variables.
1301 void resolve (CswNumber x, CswNumber y) {
1302 mResolvePair[0] = x;
1303 mResolvePair[1] = y;
1304 resolve(mResolvePair);
1307 /// Re-solve the current collection of constraints, given the new
1308 /// values for the edit variables that have already been
1309 /// suggested (see suggestValue() method).
1310 void resolve () {
1311 dualOptimize();
1312 setExternalVariables();
1313 mInfeasibleRows = mInfeasibleRows.init; //mInfeasibleRows.clear();
1314 resetStayConstants();
1317 /// suggest a new value for an edit variable.
1319 /// The variable needs to be added as an edit variable and
1320 /// beginEdit() needs to be called before this is called.
1321 /// The tableau will not be solved completely until after resolve()
1322 /// has been called.
1323 CswSimplexSolver suggestValue (CswAbstractVariable v, CswNumber x) {
1324 if (auto ceiv = v.vindex in mEditVarMap) {
1325 auto cei = ceiv.edit;
1326 immutable i = cei.index;
1327 CswSlackVariable clvEditPlus = cei.editPlus;
1328 CswSlackVariable clvEditMinus = cei.editMinus;
1329 CswNumber delta = x-cei.prevEditConstant;
1330 cei.prevEditConstant = x;
1331 deltaEditConstant(delta, clvEditPlus, clvEditMinus);
1332 return this;
1333 } else {
1334 //debug { import iv.writer; errwriteln("suggestValue for variable ", v.toString(), ", but var is not an edit variable"); }
1335 throw new CswError("suggestValue!");
1339 /// Controls wether optimization and setting of external variables is done
1340 /// automatically or not.
1342 /// By default it is done automatically and solve() never needs
1343 /// to be explicitly called by client code. If `autoSolve` is
1344 /// put to false, then solve() needs to be invoked explicitly
1345 /// before using variables' values.
1346 @property bool autoSolve () const @safe pure nothrow @nogc { return mOptimizeAutomatically; }
1347 @property void autoSolve (bool v) @safe nothrow @nogc { mOptimizeAutomatically = v; }
1349 CswSimplexSolver solve () {
1350 if (mNeedsSolving) {
1351 optimize(mObjective);
1352 setExternalVariables();
1354 return this;
1357 CswSimplexSolver setEditedValue (CswVariable v, CswNumber n) {
1358 if (!containsVariable(v)) {
1359 v.changeValue(n);
1360 return this;
1362 if (!Csw.approx(n, v.value)) {
1363 addEditVar(v);
1364 beginEdit();
1365 try {
1366 suggestValue(v, n);
1367 } catch (CswError) {
1368 // just added it above, so we shouldn't get an error
1369 //import csw.errors : CswErrorInternalError;
1370 throw new CswErrorInternalError("Error in setEditedValue");
1372 endEdit();
1374 return this;
1377 bool containsVariable (CswVariable v) nothrow { return columnsHasKey(v) || (rowExpression(v) !is null); }
1379 CswSimplexSolver addVar (CswVariable v) {
1380 if (!containsVariable(v)) {
1381 try {
1382 addStay(v);
1383 } catch (CswErrorRequiredFailure) {
1384 // cannot have a required failure, since we add w/ weak
1385 //import csw.errors : CswErrorInternalError;
1386 throw new CswErrorInternalError("Error in AddVar -- required failure is impossible");
1389 return this;
1392 /// Returns information about the solver's internals.
1394 /// Originally from Michael Noth <noth@cs.washington.edu>
1396 /// Returns:
1397 /// String containing the information.
1398 override string getInternalInfo () const {
1399 import std.string : format;
1400 string result = super.getInternalInfo();
1401 result ~= "\nSolver info:\n";
1402 result ~= "Stay Error Variables: ";
1403 result ~= "%s".format(mStayPlusErrorVars.length+mStayMinusErrorVars.length);
1404 result ~= " (%s +, ".format(mStayPlusErrorVars.length);
1405 result ~= "%s -)\n".format(mStayMinusErrorVars.length);
1406 result ~= "Edit Variables: %s".format(mEditVarMap.length);
1407 result ~= "\n";
1408 return result;
1411 string getDebugInfo () const {
1412 string result = toString();
1413 result ~= getInternalInfo();
1414 result ~= "\n";
1415 return result;
1418 override string toString () const {
1419 import std.string : format;
1420 string result = super.toString();
1421 result ~= "\nmStayPlusErrorVars: %s".format(mStayPlusErrorVars);
1422 result ~= "\nmStayMinusErrorVars: %s".format(mStayMinusErrorVars);
1423 result ~= "\n";
1424 return result;
1427 //// END PUBLIC INTERFACE ////
1429 // Add the constraint expr=0 to the inequality tableau using an
1430 // artificial variable.
1432 // To do this, create an artificial variable av and add av=expr
1433 // to the inequality tableau, then make av be 0 (raise an exception
1434 // if we can't attain av=0).
1435 protected void addWithArtificialVariable (CswLinearExpression expr) {
1436 CswSlackVariable av = new CswSlackVariable(++mArtificialCounter, "a");
1437 CswObjectiveVariable az = new CswObjectiveVariable("az");
1438 CswLinearExpression azRow = /*(CswLinearExpression)*/ expr.clone();
1440 addRow(az, azRow);
1441 addRow(av, expr);
1443 optimize(az);
1445 CswLinearExpression azTableauRow = rowExpression(az);
1446 if (!Csw.approx(azTableauRow.constant, 0.0)) {
1447 removeRow(az);
1448 removeColumn(av);
1449 throw new CswErrorRequiredFailure("!!!");
1452 // see if av is a basic variable
1453 CswLinearExpression e = rowExpression(av);
1455 if (e !is null) {
1456 // find another variable in this row and pivot,
1457 // so that av becomes parametric
1458 if (e.isConstant) {
1459 // if there isn't another variable in the row
1460 // then the tableau contains the equation av=0 --
1461 // just delete av's row
1462 removeRow(av);
1463 removeRow(az);
1464 return;
1466 CswAbstractVariable entryVar = e.anyPivotableVariable();
1467 pivot(entryVar, av);
1469 assert(rowExpression(av) is null, "rowExpression(av) == null)");
1470 removeColumn(av);
1471 removeRow(az);
1474 // Try to add expr directly to the tableau without creating an
1475 // artificial variable.
1477 // We are trying to add the constraint expr=0 to the appropriate
1478 // tableau.
1480 // Returns:
1481 // True if successful and false if not.
1482 protected bool tryAddingDirectly (CswLinearExpression expr) {
1483 CswAbstractVariable subject = chooseSubject(expr);
1484 if (subject is null) return false;
1485 expr.newSubject(subject);
1486 if (columnsHasKey(subject)) substituteOut(subject, expr);
1487 addRow(subject, expr);
1488 return true; // succesfully added directly
1491 // Try to choose a subject (a variable to become basic) from
1492 // among the current variables in expr.
1494 // We are trying to add the constraint expr=0 to the tableaux.
1495 // If expr constains any unrestricted variables, then we must choose
1496 // an unrestricted variable as the subject. Also if the subject is
1497 // new to the solver, we won't have to do any substitutions, so we
1498 // prefer new variables to ones that are currently noted as parametric.
1499 // If expr contains only restricted variables, if there is a restricted
1500 // variable with a negative coefficient that is new to the solver we can
1501 // make that the subject. Otherwise we can't find a subject, so return nil.
1502 // (In this last case we have to add an artificial variable and use that
1503 // variable as the subject -- this is done outside this method though.)
1504 protected CswAbstractVariable chooseSubject (CswLinearExpression expr) {
1505 CswAbstractVariable subject = null; // the current best subject, if any
1507 bool foundUnrestricted = false;
1508 bool foundNewRestricted = false;
1510 //auto terms = expr.mTerms;
1511 foreach (ref clv; expr.mTerms.byValue) {
1512 //CswNumber c = terms[v];
1513 auto v = clv.var;
1514 immutable c = clv.num;
1515 if (foundUnrestricted) {
1516 if (!v.isRestricted) {
1517 if (!columnsHasKey(v)) return v;
1519 } else {
1520 // we haven't found an restricted variable yet
1521 if (v.isRestricted) {
1522 if (!foundNewRestricted && !v.isDummy && c < 0.0) {
1523 auto col = v.vindex in mColumns;
1524 if (col is null || (col.set.length == 1 && columnsHasKey(mObjective))) {
1525 subject = v;
1526 foundNewRestricted = true;
1529 } else {
1530 subject = v;
1531 foundUnrestricted = true;
1535 if (subject !is null) return subject;
1537 CswNumber coeff = 0.0;
1538 foreach (ref clv; expr.mTerms.byValue) {
1539 //CswNumber c = terms[v];
1540 auto v = clv.var;
1541 immutable c = clv.num;
1542 if (!v.isDummy) return null; // nope, no luck
1543 if (!columnsHasKey(v)) {
1544 subject = v;
1545 coeff = c;
1549 if (!Csw.approx(expr.constant, 0.0)) throw new CswErrorRequiredFailure("!!!");
1550 if (coeff > 0.0) expr.multiplyMe(-1);
1552 return subject;
1555 // Make a new linear Expression representing the constraint cn,
1556 // replacing any basic variables with their defining expressions.
1557 // Normalize if necessary so that the Constant is non-negative.
1558 // If the constraint is non-required give its error variables an
1559 // appropriate weight in the objective function.
1560 protected CswLinearExpression newExpression (CswConstraint cn, out CswSlackVariable[2] ePlusEMinus,
1561 out CswNumber prevEConstant)
1563 CswLinearExpression cnExpr = cn.expression;
1564 CswLinearExpression expr = new CswLinearExpression(cnExpr.constant);
1565 CswSlackVariable slackVar = new CswSlackVariable();
1566 CswDummyVariable dummyVar = new CswDummyVariable();
1567 CswSlackVariable eminus = new CswSlackVariable();
1568 CswSlackVariable eplus = new CswSlackVariable();
1569 //auto cnTerms = cnExpr.terms;
1570 foreach (ref clv; cnExpr.mTerms.byValue) {
1571 //CswNumber c = cnTerms[v];
1572 auto v = clv.var;
1573 immutable c = clv.num;
1574 CswLinearExpression e = rowExpression(v);
1575 if (e is null) expr.addVariable(v, c); else expr.addExpression(e, c);
1577 if (cn.isInequality) {
1578 // cn is an inequality, so Add a slack variable. The original constraint
1579 // is expr>=0, so that the resulting equality is expr-slackVar=0. If cn is
1580 // also non-required Add a negative error variable, giving:
1582 // expr - slackVar = -errorVar
1584 // in other words:
1586 // expr - slackVar + errorVar = 0
1588 // Since both of these variables are newly created we can just Add
1589 // them to the Expression (they can't be basic).
1590 ++mSlackCounter;
1591 slackVar = new CswSlackVariable(mSlackCounter, "s");
1592 expr.setVariable(slackVar, -1);
1593 mMarkerVars[cn.cindex] = MKV(cn, slackVar);
1594 if (!cn.isRequired) {
1595 ++mSlackCounter;
1596 eminus = new CswSlackVariable(mSlackCounter, "em");
1597 expr.setVariable(eminus, 1.0);
1598 CswLinearExpression zRow = rowExpression(mObjective);
1599 zRow.setVariable(eminus, cn.strength*cn.weight);
1600 insertErrorVar(cn, eminus);
1601 noteAddedVariable(eminus, mObjective);
1603 } else {
1604 // cn is an equality
1605 if (cn.isRequired) {
1606 // Add a dummy variable to the Expression to serve as a marker for this constraint.
1607 // The dummy variable is never allowed to enter the basis when pivoting.
1608 ++mDummyCounter;
1609 dummyVar = new CswDummyVariable(mDummyCounter, "d");
1610 expr.setVariable(dummyVar, 1.0);
1611 mMarkerVars[cn.cindex] = MKV(cn, dummyVar);
1612 } else {
1613 // cn is a non-required equality. Add a positive and a negative error
1614 // variable, making the resulting constraint
1615 // expr = eplus - eminus
1616 // in other words:
1617 // expr - eplus + eminus = 0
1618 ++mSlackCounter;
1619 eplus = new CswSlackVariable(mSlackCounter, "ep");
1620 eminus = new CswSlackVariable(mSlackCounter, "em");
1622 expr.setVariable(eplus, -1.0);
1623 expr.setVariable(eminus, 1.0);
1624 mMarkerVars[cn.cindex] = MKV(cn, eplus);
1625 CswLinearExpression zRow = rowExpression(mObjective);
1626 immutable swCoeff = cn.strength*cn.weight;
1627 zRow.setVariable(eplus, swCoeff);
1628 noteAddedVariable(eplus, mObjective);
1629 zRow.setVariable(eminus, swCoeff);
1630 noteAddedVariable(eminus, mObjective);
1631 insertErrorVar(cn, eminus);
1632 insertErrorVar(cn, eplus);
1633 if (cn.isStayConstraint) {
1634 mStayPlusErrorVars ~= eplus;
1635 mStayMinusErrorVars ~= eminus;
1636 } else if (cn.isEditConstraint) {
1637 ePlusEMinus[0] = eplus;
1638 ePlusEMinus[1] = eminus;
1639 prevEConstant = cnExpr.constant;
1643 // the Constant in the Expression should be non-negative. If necessary
1644 // normalize the Expression by multiplying by -1
1645 if (expr.constant < 0) expr.multiplyMe(-1);
1646 return expr;
1649 // Minimize the value of the objective.
1651 // The tableau should already be feasible.
1652 protected void optimize (CswObjectiveVariable zVar) {
1653 CswLinearExpression zRow = rowExpression(zVar);
1654 assert(zRow !is null, "zRow != null");
1655 CswAbstractVariable entryVar = null;
1656 CswAbstractVariable exitVar = null;
1657 for (;;) {
1658 CswNumber objectiveCoeff = 0;
1659 //auto terms = zRow.terms;
1660 // Find the most negative coefficient in the objective function (ignoring
1661 // the non-pivotable dummy variables). If all coefficients are positive
1662 // we're done
1663 foreach (ref clv; zRow.mTerms.byValue) {
1664 //CswNumber c = terms[v];
1665 auto v = clv.var;
1666 immutable c = clv.num;
1667 if (v.isPivotable && c < objectiveCoeff) {
1668 objectiveCoeff = c;
1669 entryVar = v;
1672 if (objectiveCoeff >= -mEpsilon || entryVar is null) return;
1673 // choose which variable to move out of the basis
1674 // Only consider pivotable basic variables
1675 // (i.e. restricted, non-dummy variables)
1676 CswNumber minRatio = CswNumber.max;
1677 auto columnVars = mColumns[entryVar.vindex];
1678 CswNumber r = 0.0;
1679 foreach (/*auto*/ v; columnVars.set) {
1680 if (v.isPivotable) {
1681 CswLinearExpression expr = rowExpression(v);
1682 CswNumber coeff = expr.coefficientFor(entryVar);
1683 if (coeff < 0.0) {
1684 r = -expr.constant/coeff;
1685 // Bland's anti-cycling rule:
1686 // if multiple variables are about the same,
1687 // always pick the lowest via some total
1688 // ordering -- I use their addresses in memory
1689 // if (r < minRatio ||
1690 // (c.approx(r, minRatio) &&
1691 // v.get_pclv() < exitVar.get_pclv()))
1692 if (r < minRatio) {
1693 minRatio = r;
1694 exitVar = v;
1699 // If minRatio is still nil at this point, it means that the
1700 // objective function is unbounded, i.e. it can become
1701 // arbitrarily negative. This should never happen in this
1702 // application.
1703 if (minRatio == CswNumber.max) {
1704 //import csw.errors : CswErrorInternalError;
1705 throw new CswErrorInternalError("Objective function is unbounded in optimize");
1707 pivot(entryVar, exitVar);
1711 // Fix the constants in the equations representing the edit constraints.
1713 // Each of the non-required edits will be represented by an equation
1714 // of the form:
1715 // v = c + eplus - eminus
1716 // where v is the variable with the edit, c is the previous edit value,
1717 // and eplus and eminus are slack variables that hold the error in
1718 // satisfying the edit constraint. We are about to change something,
1719 // and we want to fix the constants in the equations representing
1720 // the edit constraints. If one of eplus and eminus is basic, the other
1721 // must occur only in the expression for that basic error variable.
1722 // (They can't both be basic.) Fix the constant in this expression.
1723 // Otherwise they are both non-basic. Find all of the expressions
1724 // in which they occur, and fix the constants in those. See the
1725 // UIST paper for details.
1726 // (This comment was for ResetEditConstants(), but that is now
1727 // gone since it was part of the screwey vector-based interface
1728 // to resolveing. --02/16/99 gjb)
1729 protected void deltaEditConstant (CswNumber delta, CswAbstractVariable plusErrorVar, CswAbstractVariable minusErrorVar) {
1730 CswLinearExpression exprPlus = rowExpression(plusErrorVar);
1731 if (exprPlus !is null) {
1732 exprPlus.incrementConstant(delta);
1733 if (exprPlus.constant < 0.0) mInfeasibleRows[plusErrorVar.vindex] = plusErrorVar;
1734 return;
1737 CswLinearExpression exprMinus = rowExpression(minusErrorVar);
1738 if (exprMinus !is null) {
1739 exprMinus.incrementConstant(-delta);
1740 if (exprMinus.constant < 0.0) mInfeasibleRows[minusErrorVar.vindex] = minusErrorVar;
1741 return;
1744 auto columnVars = mColumns[minusErrorVar.vindex];
1745 foreach (/*auto*/ basicVar; columnVars.set) {
1746 CswLinearExpression expr = rowExpression(basicVar);
1747 //assert(expr != null, "expr != null");
1748 CswNumber c = expr.coefficientFor(minusErrorVar);
1749 expr.incrementConstant(c*delta);
1750 if (basicVar.isRestricted && expr.constant < 0.0) mInfeasibleRows[basicVar.vindex] = basicVar;
1754 // Re-optimize using the dual simplex algorithm.
1756 // We have set new values for the constants in the edit constraints.
1757 protected void dualOptimize () {
1758 CswLinearExpression zRow = rowExpression(mObjective);
1759 while (mInfeasibleRows.length) {
1760 // get first var
1761 CswAbstractVariable exitVar = mInfeasibleRows.byValue.front;
1762 mInfeasibleRows.remove(exitVar.vindex);
1763 CswAbstractVariable entryVar = null;
1764 CswLinearExpression expr = rowExpression(exitVar);
1765 if (expr !is null) {
1766 if (expr.constant < 0.0) {
1767 CswNumber ratio = CswNumber.max;
1768 CswNumber r;
1769 //auto terms = expr.terms;
1770 foreach (ref clv; expr.mTerms.byValue) {
1771 //CswNumber c = terms[v];
1772 auto v = clv.var;
1773 immutable c = clv.num;
1774 if (c > 0.0 && v.isPivotable) {
1775 CswNumber zc = zRow.coefficientFor(v);
1776 r = zc / c; // FIXME: zc / c or zero, as CswSymbolicWeigth-s
1777 if (r < ratio) {
1778 entryVar = v;
1779 ratio = r;
1783 if (ratio == CswNumber.max) {
1784 //import csw.errors : CswErrorInternalError;
1785 throw new CswErrorInternalError("ratio == nil (Double.MaxValue) in dualOptimize");
1787 pivot(entryVar, exitVar);
1793 // Do a pivot. Move entryVar into the basis and move exitVar
1794 // out of the basis.
1796 // We could for example make entryVar a basic variable and
1797 // make exitVar a parametric variable.
1798 protected void pivot (CswAbstractVariable entryVar, CswAbstractVariable exitVar) {
1799 // the entryVar might be non-pivotable if we're doing a
1800 // removeConstraint -- otherwise it should be a pivotable
1801 // variable -- enforced at call sites, hopefully.
1802 // expr is the Expression for the exit variable (about to leave the basis) --
1803 // so that the old tableau includes the equation:
1804 // exitVar = expr
1805 CswLinearExpression expr = removeRow(exitVar);
1806 // Compute an Expression for the entry variable. Since expr has
1807 // been deleted from the tableau we can destructively modify it to
1808 // build this Expression.
1809 expr.changeSubject(exitVar, entryVar);
1810 substituteOut(entryVar, expr);
1811 addRow(entryVar, expr);
1814 // Fix the constants in the equations representing the stays.
1816 // Each of the non-required stays will be represented by an equation
1817 // of the form
1818 // v = c + eplus - eminus
1819 // where v is the variable with the stay, c is the previous value
1820 // of v, and eplus and eminus are slack variables that hold the error
1821 // in satisfying the stay constraint. We are about to change something,
1822 // and we want to fix the constants in the equations representing the
1823 // stays. If both eplus and eminus are nonbasic they have value 0
1824 // in the current solution, meaning the previous stay was exactly
1825 // satisfied. In this case nothing needs to be changed. Otherwise one
1826 // of them is basic, and the other must occur only in the expression
1827 // for that basic error variable. Reset the constant of this
1828 // expression to 0.
1829 protected void resetStayConstants () {
1830 foreach (immutable i; 0..mStayPlusErrorVars.length) {
1831 CswLinearExpression expr = rowExpression(mStayPlusErrorVars[i]);
1832 if (expr is null) expr = rowExpression(mStayMinusErrorVars[i]);
1833 if (expr !is null) expr.constant = 0.0;
1837 // CswSet the external variables known to this solver to their appropriate values.
1839 // CswSet each external basic variable to its value, and set each external parametric
1840 // variable to 0. (It isn't clear that we will ever have external parametric
1841 // variables -- every external variable should either have a stay on it, or have an
1842 // equation that defines it in terms of other external variables that do have stays.
1843 // For the moment I'll put this in though.) Variables that are internal to the solver
1844 // don't actually store values -- their values are just implicit in the tableau -- so
1845 // we don't need to set them.
1846 protected void setExternalVariables () {
1847 foreach (/*auto*/ v; mExternalParametricVars.byValue) {
1848 if (rowExpression(v) !is null) {
1849 //debug { import iv.writer; errwriteln("Error: variable ", v.toString(), "in mExternalParametricVars is basic"); }
1850 continue;
1852 auto vv = cast(CswVariable)v;
1853 vv.changeValue(0.0);
1855 foreach (/*auto*/ v; mExternalRows.byValue) {
1856 CswLinearExpression expr = rowExpression(v);
1857 auto vv = cast(CswVariable)v;
1858 vv.changeValue(expr.constant);
1860 mNeedsSolving = false;
1863 // Protected convenience function to insert an error variable
1864 // into the mErrorVars set, creating the mapping with Add as necessary.
1865 protected void insertErrorVar (CswConstraint cn, CswAbstractVariable var) {
1866 if (auto cnset = cn.cindex in mErrorVars) {
1867 cnset.vars[var.vindex] = var;
1868 } else {
1869 //CswAbstractVariable[CswAbstractVariable] ev;
1870 ErrVar ev;
1871 ev.cst = cn;
1872 ev.vars[var.vindex] = var;
1873 mErrorVars[cn.cindex] = ev;
1877 public:
1878 @property ref CswVariable[string] varMap () @safe nothrow @nogc { return mVarMap; }
1879 @property void varMap (ref CswVariable[string] v) @safe nothrow @nogc { mVarMap = v; }
1883 // ////////////////////////////////////////////////////////////////////////// //
1884 // variables
1885 class CswAbstractVariable {
1886 private:
1887 static uint mVarIndex; // so we can't have more that 0xffffffff variables for the thread lifetime
1889 @property static uint newVarIndex () @trusted nothrow @nogc {
1890 if (++mVarIndex == 0) assert(0, "too many variables in Cassowary!"); // the thing that should not be
1891 return mVarIndex;
1894 private:
1895 uint vindex;
1897 public:
1898 string name;
1900 public:
1901 @safe:
1902 nothrow:
1903 this () { name = buildIndexedName("v", (vindex = newVarIndex)); }
1904 this (string aname) @nogc { vindex = newVarIndex; name = aname; }
1905 this (uint varnumber, string prefix) { name = buildIndexedName(prefix, (vindex = newVarIndex), varnumber); }
1907 const pure @nogc {
1908 @property bool isDummy () const { return false; }
1909 abstract @property bool isExternal ();
1910 abstract @property bool isPivotable ();
1911 abstract @property bool isRestricted ();
1914 @property static uint count () @nogc { return mVarIndex; }
1916 override string toString () const nothrow { return "["~name~":abstract]"; }
1918 protected:
1919 // 4294967296
1920 static string buildIndexedName (string pfx, uint idx, uint num=uint.max) @trusted nothrow {
1921 char[21] n;
1922 usize pos = n.length;
1923 // secondary index
1924 if (num != uint.max) {
1925 do {
1926 n[--pos] = num%10+'0';
1927 num /= 10;
1928 } while (num != 0);
1929 n[--pos] = '#';
1931 // primary index
1932 do {
1933 n[--pos] = idx%10+'0';
1934 idx /= 10;
1935 } while (idx != 0);
1936 import std.exception : assumeUnique;
1937 auto res = new char[](pfx.length+(n.length-pos));
1938 if (pfx.length) res[0..pfx.length] = pfx[];
1939 res[pfx.length..$] = n[pos..$];
1940 return res.assumeUnique;
1943 template HasStr (string s, T...) {
1944 static if (T.length == 0)
1945 enum HasStr = false;
1946 else static if (T[0] == s)
1947 enum HasStr = true;
1948 else
1949 enum HasStr = HasStr!(s, T[1..$]);
1952 template IFS (bool v, string t="true", string f="false") {
1953 static if (v)
1954 enum IFS = t;
1955 else
1956 enum IFS = f;
1959 template GenTypes (T...) {
1960 private enum dum = HasStr!("dummy", T);
1961 private enum ext = HasStr!("extern", T) || HasStr!("external", T);
1962 private enum piv = HasStr!("pivot", T) || HasStr!("pivotable", T);
1963 private enum res = HasStr!("restricted", T);
1964 enum GenTypes =
1965 "override @property bool isDummy () const @safe pure nothrow @nogc { return "~IFS!(dum)~"; }\n"~
1966 "override @property bool isExternal () const @safe pure nothrow @nogc { return "~IFS!(ext)~"; }\n"~
1967 "override @property bool isPivotable () const @safe pure nothrow @nogc { return "~IFS!(piv)~"; }\n"~
1968 "override @property bool isRestricted () const @safe pure nothrow @nogc { return "~IFS!(res)~"; }\n";
1973 class CswDummyVariable : CswAbstractVariable {
1974 this () @safe nothrow { super(); }
1975 this (string name) @safe nothrow @nogc { super(name); }
1976 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1977 override string toString () const nothrow { return "["~name~":dummy]"; }
1978 mixin(GenTypes!("dummy", "restricted"));
1982 class CswSlackVariable : CswAbstractVariable {
1983 this () @safe nothrow { super(); }
1984 this (string name) @safe nothrow @nogc { super(name); }
1985 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1986 override string toString () const nothrow { return "["~name~":slack]"; }
1987 mixin(GenTypes!("pivotable", "restricted"));
1991 class CswObjectiveVariable : CswAbstractVariable {
1992 this () @safe nothrow { super(); }
1993 this (string name) @safe nothrow @nogc { super(name); }
1994 this (uint varnumber, string prefix) @safe nothrow { super(varnumber, prefix); }
1995 override string toString () const nothrow { return "["~name~":obj]"; }
1996 mixin(GenTypes!());
2000 class CswVariable : CswAbstractVariable {
2001 private:
2002 CswNumber mValue;
2004 public:
2005 @safe nothrow {
2006 this () { super(); mValue = 0.0; }
2007 this (CswNumber value) { super(); mValue = value; }
2008 this (string name, CswNumber value=0.0) @nogc { super(name); mValue = value; }
2009 this (uint number, string prefix, CswNumber value=0.0) { super(number, prefix); mValue = value; }
2010 this (ref CswVariable[string] varMap, string name, CswNumber value=0.0) { this(name, value); varMap[name] = this; }
2013 override string toString () const nothrow @trusted/*gdc*/ {
2014 try {
2015 import std.conv : to;
2016 return "["~name~":"~to!string(mValue)~"]";
2017 } catch (Exception) {
2018 return "["~name~":???]";
2022 mixin(GenTypes!("external"));
2024 // Change the value held -- should *not* use this if the variable is
2025 // in a solver -- use addEditVar() and suggestValue() interface instead
2026 @property CswNumber value () const @safe pure nothrow @nogc { return mValue; }
2027 @property void value (CswNumber v) @safe nothrow @nogc { mValue = v; }
2029 // Permit overriding in subclasses in case something needs to be
2030 // done when the value is changed by the solver
2031 // may be called when the value hasn't actually changed -- just
2032 // means the solver is setting the external variable
2033 public void changeValue (CswNumber value) @safe pure nothrow @nogc { mValue = value; }
2035 // construct expressions
2036 mixin(buildOpBin!(`*/`, `CswNumber`));
2037 mixin(buildOpBin!(`+-*/`, `CswLinearExpression`));
2038 mixin(buildOpBin!(`+-`, `CswVariable`));
2040 // convert variable to CswLinearExpression
2041 final T opCast(T : CswLinearExpression) () { return new CswLinearExpression(this); }
2043 private:
2044 template buildOpBinConstraint(string ops) {
2045 static if (ops.length > 1)
2046 enum buildOpBinConstraint = `op == "`~ops[0]~`" || `~buildOpBinConstraint!(ops[1..$]);
2047 else
2048 enum buildOpBinConstraint = `op == "`~ops~`"`;
2051 enum buildOpBin(string ops, string argtype) =
2052 `final CswLinearExpression opBinary(string op) (`~argtype~` n)`~
2053 `if (`~buildOpBinConstraint!ops~`) {`~
2054 ` auto e0 = new CswLinearExpression(this);`~
2055 ` mixin("return e0"~op~"n;");`~
2056 `}`;
2060 // ////////////////////////////////////////////////////////////////////////// //
2061 // parser
2062 private:
2063 debug import std.stdio;
2064 debug(CswParser) import std.stdio;
2065 debug(CswTokenizer) import std.stdio;
2068 // ////////////////////////////////////////////////////////////////////////// //
2069 struct Token {
2070 static immutable string oneChars = "=+-*/()[]<>,;:"; // WARNING! keep in sync with Type enum!
2071 enum Type {
2072 EOF,
2074 Number,
2075 EqEq, GEq, LEq, // multichar tokens
2076 // here starts one-char tokens; must be in sync with oneChars
2077 Eq, Plus, Minus, Times, Divide, LParen, RParen, LBrac, RBrac, Less, Great, Comma, Semicolon, Colon
2080 usize pos; // token position in stream
2081 Type type = Type.EOF;
2082 CswNumber n;
2083 string s;
2085 @property bool isEOF () const @safe pure nothrow @nogc { return (type == Type.EOF); }
2086 @property bool isEOX () const @safe pure nothrow @nogc { return (type == Type.EOF || type == Type.Semicolon); }
2087 @property bool isId () const @safe pure nothrow @nogc { return (type == Type.Id); }
2088 @property bool isNumber () const @safe pure nothrow @nogc { return (type == Type.Number); }
2089 @property bool isPunct () const @safe pure nothrow @nogc { return (type > Type.Number && type <= Type.max); }
2091 string toString () const {
2092 import std.conv : to;
2093 switch (type) {
2094 case Type.EOF: return "{EOF}";
2095 case Type.Id: return "{id:"~s~"}";
2096 case Type.Number: return "{num:"~to!string(n)~"}";
2097 case Type.EqEq: return "==";
2098 case Type.GEq: return ">=";
2099 case Type.LEq: return "<=";
2100 default:
2101 if (type >= Type.Eq && type <= Type.max) return to!string(oneChars[type-Type.Eq]);
2102 return "{invalid}";
2108 void skipBlanks (ref ParserData st) {
2109 import std.uni;
2110 for (;;) {
2111 auto ch = st.getChar();
2112 if (ch == 0) return; // end of stream
2113 if (ch <= 32 || std.uni.isWhite(ch)) continue; // skip this char
2114 // slash-slash or slash-star?
2115 if (ch == '/') {
2116 ch = st.getChar(); // skip first slash
2117 if (ch == '*') {
2118 // multiline comment
2119 for (;;) {
2120 ch = st.getChar();
2121 // star-slash?
2122 if (ch == '*') {
2123 ch = st.getChar();
2124 if (ch == '/') break;
2127 continue;
2129 // slash-slash?
2130 if (ch != '/') {
2131 // alas, unget 'em both
2132 st.ungetChar(ch);
2133 st.ungetChar('/');
2134 return;
2136 ch = '#'; // comment-to-eol
2138 // comment-to-eol?
2139 if (ch == '#') {
2140 do { ch = st.getChar(); } while (ch != 0 && ch != '\n');
2141 continue;
2143 // other non-white and non-comment chars
2144 st.ungetChar(ch);
2145 return;
2150 Token getToken (ref ParserData st) {
2151 static bool isId (dchar ch) {
2152 import std.uni : isAlpha;
2153 return
2154 (ch >= '0' && ch <= '9') ||
2155 (ch >= 'A' && ch <= 'Z') ||
2156 (ch >= 'a' && ch <= 'z') ||
2157 ch == '_' || ch == '.' ||
2158 isAlpha(ch);
2161 dchar ch;
2163 skipBlanks(st);
2164 st.lastTokenPos = st.pos;
2165 ch = st.getChar();
2166 debug(CswTokenizer) writeln("lastTokenPos=", st.lastTokenPos, "; ch=", ch);
2167 if (ch == 0) return Token(st.lastTokenPos, Token.Type.EOF);
2169 // try "{?}="
2170 if (ch == '<' || ch == '>' || ch == '=') {
2171 dchar ch1 = st.getChar();
2172 debug(CswTokenizer) writeln(" ?2char; ch=", ch, "; ch1=", ch1);
2173 if (ch1 == '=') {
2174 Token.Type tt = void;
2175 final switch (ch) {
2176 case '<': tt = Token.Type.LEq; break;
2177 case '>': tt = Token.Type.GEq; break;
2178 case '=': tt = Token.Type.EqEq; break;
2180 return Token(st.lastTokenPos, tt);
2182 // restore char
2183 st.ungetChar(ch1);
2186 // one-char token?
2187 if (ch < 127) {
2188 import std.string : indexOf;
2189 debug(CswTokenizer) writeln(" ?punct; ch=", ch);
2190 auto pp = Token.oneChars.indexOf(cast(char)ch);
2191 if (pp >= 0) return Token(st.lastTokenPos, cast(Token.Type)(Token.Type.Eq+pp));
2194 // number?
2195 if (ch >= '0' && ch <= '9') {
2196 CswNumber n = 0.0;
2197 while (ch >= '0' && ch <= '9') {
2198 n = n*10+ch-'0';
2199 ch = st.getChar();
2201 if (ch == '.') {
2202 CswNumber frc = 0.1;
2203 ch = st.getChar();
2204 while (ch >= '0' && ch <= '9') {
2205 n += (ch-'0')*frc;
2206 frc /= 10.0;
2207 ch = st.getChar();
2210 st.ungetChar(ch);
2211 debug(CswTokenizer) writeln(" number=", n);
2212 return Token(st.lastTokenPos, Token.Type.Number, n);
2215 // id?
2216 if (ch != '.' && isId(ch)) {
2217 import std.array : appender;
2218 auto id = appender!string();
2219 while (isId(ch)) {
2220 id.put(ch);
2221 ch = st.getChar();
2223 st.ungetChar(ch);
2224 debug(CswTokenizer) writeln(" id=", id.data);
2225 return Token(st.lastTokenPos, Token.Type.Id, 0.0, id.data);
2228 throw new CswErrorParser("invalid token");
2229 assert(0);
2233 // ////////////////////////////////////////////////////////////////////////// //
2234 struct Operator {
2235 enum Assoc { None, Left, Right, Unary }
2236 dchar math;
2237 Token.Type ttype = Token.Type.EOF;
2238 Assoc assoc = Assoc.None;
2239 int prio = -666;
2241 string toString () const {
2242 import std.conv : to;
2243 string s = "["~to!string(math)~"]";
2244 final switch (assoc) {
2245 case Assoc.None: s ~= " (none)"; break;
2246 case Assoc.Left: s ~= " (left)"; break;
2247 case Assoc.Right: s ~= " (right)"; break;
2248 case Assoc.Unary: s ~= " (unary)"; break;
2250 s ~= " (pr:"~to!string(prio)~")";
2251 return s;
2256 struct Term {
2257 enum Type { EOF, Number, Var, Expr, Operator }
2259 Type type = Type.EOF;
2260 CswNumber n;
2261 CswVariable v;
2262 CswLinearExpression e;
2263 Operator op = void;
2265 this (CswNumber an) @safe nothrow @nogc { type = Type.Number; n = an; }
2266 this (CswVariable av) @safe nothrow @nogc { type = Type.Var; v = av; }
2267 this (CswLinearExpression ae) @safe nothrow @nogc { type = Type.Expr; e = ae; }
2268 this (in Operator aop) @safe nothrow @nogc { type = Type.Operator; op = aop; }
2270 @property bool isEOF () const @safe pure nothrow @nogc { return (type == Type.EOF); }
2271 @property bool isNumber () const @safe pure nothrow @nogc { return (type == Type.Number); }
2272 @property bool isVar () const @safe pure nothrow @nogc { return (type == Type.Var); }
2273 @property bool isExpr () const @safe pure nothrow @nogc { return (type == Type.Expr); }
2274 @property bool isOperator () const @safe pure nothrow @nogc { return (type == Type.Operator); }
2276 T opCast(T : CswLinearExpression) () {
2277 switch (type) {
2278 case Type.Number: return new CswLinearExpression(n);
2279 case Type.Var: return new CswLinearExpression(v);
2280 case Type.Expr: return e;
2281 default: throw new CswErrorParser(`can't cast term to CswLinearExpression`);
2285 string toString () const {
2286 import std.conv : to;
2287 final switch (type) {
2288 case Type.EOF: return "<EOF>";
2289 case Type.Number: return "{num:"~to!string(n)~"}";
2290 case Type.Var: return "{var:"~v.toString()~"}";
2291 case Type.Expr: return "{expr:"~e.toString()~"}";
2292 case Type.Operator: return "{op:"~op.toString()~"}";
2298 static immutable Operator[/*$*/] operators = [
2299 {'(', Token.Type.LParen, Operator.Assoc.Left, -1},
2300 {')', Token.Type.RParen, Operator.Assoc.None, -1},
2301 //{"**", Token.Type.EOF, Operator.Assoc.Right, 4},
2302 //{"^", Token.Type.EOF, Operator.Assoc.Right, 4},
2303 {'*', Token.Type.Times, Operator.Assoc.Left, 2},
2304 {'/', Token.Type.Divide, Operator.Assoc.Left, 2},
2305 {'+', Token.Type.Plus, Operator.Assoc.Left, 1},
2306 {'-', Token.Type.Minus, Operator.Assoc.Left, 1},
2309 static immutable Operator opUnaryMinus = {'-', Token.Type.Minus, Operator.Assoc.Unary, 3};
2312 // ////////////////////////////////////////////////////////////////////////// //
2313 struct ParserData {
2314 enum ExprMode { Constraint, SimpleMath }
2316 string sstr;
2317 CswSimplexSolver solver;
2318 ExprMode mode = ExprMode.Constraint;
2319 usize pos; // current stream position
2320 usize lastTokenPos;
2321 Token tk;
2322 bool tkValid;
2323 dchar[4] savedCh;
2324 usize savedChCount;
2326 void ungetChar (dchar ch) {
2327 if (savedChCount == savedCh.length) throw new CswErrorParser("too many ungetChar()s");
2328 if (ch != 0) {
2329 import std.utf : codeLength;
2330 usize clen = codeLength!dchar(ch);
2331 if (pos > clen) pos -= clen; else pos = 0;
2332 savedCh[savedChCount++] = ch;
2336 dchar getChar () {
2337 usize clen = void;
2338 dchar ch = void;
2339 if (savedChCount) {
2340 import std.utf : codeLength;
2341 ch = savedCh[--savedChCount];
2342 clen = codeLength!dchar(ch);
2343 } else {
2344 import std.utf : decodeFront;
2345 if (sstr.length == 0) return 0; // 0 means EOF
2346 ch = sstr.decodeFront(clen);
2348 pos += clen;
2349 if (ch == 0) ch = ' ';
2350 return ch;
2353 //FIXME: make this faster!
2354 void prependString (string s) {
2355 if (s.length) {
2356 while (savedChCount) {
2357 import std.conv : to;
2358 sstr = to!string(savedCh[--savedChCount])~sstr;
2360 sstr = s~" "~sstr;
2364 Token nextToken () {
2365 if (tkValid) {
2366 tkValid = false;
2367 return tk;
2369 return getToken(this);
2372 Token peekToken () {
2373 if (!tkValid) {
2374 tk = getToken(this);
2375 tkValid = true;
2377 return tk;
2380 void ungetToken (Token atk) {
2381 if (atk.isEOF) return;
2382 if (tkValid) throw new CswErrorParser("internal ungetToken error");
2383 tkValid = true;
2384 tk = atk;
2389 // ////////////////////////////////////////////////////////////////////////// //
2390 Term term (ref ParserData st) {
2391 again:
2392 auto tk = st.nextToken();
2393 if (tk.isEOX) {
2394 st.ungetToken(tk);
2395 return Term();
2397 if (tk.isNumber) return Term(tk.n);
2398 // id?
2399 if (tk.isId) {
2400 // variable?
2401 if (st.solver.hasVariable(tk.s)) {
2402 debug(CswParser) writeln("Term: var '", tk.s, "'");
2403 auto v = st.solver[tk.s];
2404 if (st.mode == ParserData.ExprMode.Constraint) return Term(v);
2405 return Term(v.value); // just a number
2407 // define?
2408 if (st.solver.hasDefine(tk.s)) {
2409 // insert and reparse
2410 // TODO: limit insertions!
2411 auto val = st.solver.define(tk.s);
2412 debug(CswParser) writeln("Term: define '", tk.s, "' = '", val, "'");
2413 st.prependString(val);
2414 goto again;
2416 debug(CswParser) { errwriteln("var not found: '", tk.s, "'"); }
2417 throw new CswErrorParser("var not found: '"~tk.s~"'");
2419 // operator?
2420 if (tk.isPunct) {
2421 // this can be converted to AA, but...
2422 debug(CswParser) writeln("trying punct: ", tk.type);
2423 foreach (immutable op; operators) {
2424 if (op.ttype == tk.type) {
2425 // got it!
2426 debug(CswParser) writeln(" GOT PUNCT!");
2427 return Term(op);
2432 //debug(CswParser) writeln("rest: '", st.sstr, "'");
2433 debug(CswParser) writeln("tk: '", tk, "'");
2434 st.ungetToken(tk);
2435 return Term(); // assume EOF
2439 // ////////////////////////////////////////////////////////////////////////// //
2440 Term expr (ref ParserData st) {
2441 int privBooster = 0;
2442 Term[256] stack, queue; // this should be enough for everyone, hehe
2443 usize sp, qp; // autoinit
2445 void doOperator (ref Term tk) {
2446 debug(CswParser) writeln("doOperator: ", tk);
2447 assert(tk.isOperator);
2448 if (tk.op.assoc == Operator.Assoc.Unary) {
2449 if (qp < 1) throw new CswErrorParser("invalid expression");
2450 debug(CswParser) writeln("op0: ", queue[qp-1]);
2451 if (tk.op.math == '+') return; // no-op
2452 if (queue[qp-1].isNumber) {
2453 queue[qp-1].n = -queue[qp-1].n;
2454 } else {
2455 auto eres = (cast(CswLinearExpression)queue[qp-1])*(-1.0);
2456 queue[qp-1] = Term(eres);
2458 return;
2460 // for now we has only 2ops, so...
2461 if (qp < 2) throw new CswErrorParser("invalid expression");
2462 debug(CswParser) writeln("op0: ", queue[qp-2]);
2463 debug(CswParser) writeln("op1: ", queue[qp-1]);
2464 // let's do that in this funny way
2465 if (queue[qp-2].isNumber && queue[qp-1].isNumber) {
2466 // both operands are numbers, do a little optimisation here
2467 switch (tk.op.math) {
2468 case '+': queue[qp-2].n += queue[qp-1].n; --qp; return;
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 default:
2475 // if one of the operans is number (0.0 or 1.0), do a little optimisation too
2476 // check 1st
2477 if (queue[qp-2].isNumber) {
2478 if (queue[qp-2].n == 0.0) {
2479 // annihilate both, replace with zero
2480 if (tk.op.math == '+') { queue[qp-2] = queue[qp-1]; --qp; return; } // no-op
2481 else if (tk.op.math == '-') {
2482 // negate
2483 auto eres = (cast(CswLinearExpression)queue[qp-1])*(-1.0);
2484 queue[qp-2] = Term(eres);
2485 --qp;
2486 return;
2488 else if (tk.op.math == '*') { --qp; return; } // this is 0.0
2489 } else if (queue[qp-2].n == 1.0) {
2490 if (tk.op.math == '*' || tk.op.math == '/') {
2491 // no-op
2492 queue[qp-2] = queue[qp-1];
2493 --qp;
2494 return;
2498 // check 2nd
2499 else if (queue[qp-1].isNumber) {
2500 if (queue[qp-1].n == 0.0) {
2501 if (tk.op.math == '+' || tk.op.math == '-') { --qp; return; } // no-op
2502 else if (tk.op.math == '*') {
2503 // no-op
2504 queue[qp-2] = queue[qp-1];
2505 --qp;
2506 return;
2509 else if (queue[qp-1].n == 1.0) {
2510 // leave only first operand
2511 if (tk.op.math == '*') { --qp; return; } // no-op
2514 // do it the hard way
2515 auto eres = CswLinearExpression.doMath(tk.op.math,
2516 cast(CswLinearExpression)queue[qp-2],
2517 cast(CswLinearExpression)queue[qp-1]);
2518 --qp; // drop the last operand
2519 // and replace the first one
2520 queue[qp-1] = Term(eres);
2523 for (;;) {
2524 auto tk = term(st);
2525 if (tk.isEOF) throw new CswErrorParser("unexpected end of expression");
2526 debug(CswParser) writeln("arg: ", tk);
2528 // odd logic here: don't actually stack the parens: don't need to
2529 if (tk.isOperator) {
2530 if (tk.op.ttype == Token.Type.Plus) continue; // unary plus is actually no-op
2531 if (tk.op.ttype == Token.Type.Minus) {
2532 // unary minus
2533 if (sp > 0 && stack[sp-1].isOperator && stack[sp-1].op.ttype == Token.Type.Minus) {
2534 // two unary minuses gives no-op
2535 --sp;
2536 continue;
2538 if (sp >= stack.length) throw new CswErrorParser("expression too complex");
2539 stack[sp++] = Term(opUnaryMinus);
2540 continue;
2542 // LParen is the only other operator allowed here
2543 if (tk.op.ttype == Token.Type.LParen) {
2544 privBooster += 100; // must be higher than max priority
2545 if (privBooster < 0) throw new CswErrorParser("too many parens"); // booster overflowed, heh
2546 continue;
2548 throw new CswErrorParser("invalid expression");
2550 // numbers, vars and exprs are ok here (actually, there can be no expr)
2551 assert(!tk.isExpr);
2553 // put argument to queue
2554 if (qp >= queue.length) throw new CswErrorParser("expression too complex");
2555 queue[qp++] = tk;
2557 // now process operators
2559 another_operator:
2560 tk = term(st);
2561 debug(CswParser) writeln("opr: ", tk);
2562 if (tk.isEOF) {
2563 //if (privBooster) throw new CswErrorParser("unmatched '('");
2564 debug(CswParser) writeln("EXPR DONE");
2565 break; // out of main loop
2568 if (!tk.isOperator) throw new CswErrorParser("operator expected, got "~tk.toString);
2569 if (tk.op.ttype == Token.type.LParen) throw new CswErrorParser("unexpected '(' (internal error)");
2571 bool isRParen = (tk.op.ttype == Token.type.RParen);
2572 if (tk.op.prio > 0) {
2573 // normal operator
2574 tk.op.prio += privBooster;
2575 } else if (isRParen) {
2576 // RParen
2577 if (privBooster < 100) throw new CswErrorParser("unmatched ')'");
2578 tk.op.prio = privBooster;
2581 // process operator stack
2582 while (sp) {
2583 auto t = &stack[sp-1];
2584 if (t.op.prio <= tk.op.prio) {
2585 // stacked prio is less or equal to current
2586 // stop popping if priorities arent equal or operator on the stack is right-associative
2587 if (t.op.prio != tk.op.prio || t.op.assoc == Operator.Assoc.Right) break;
2589 if (tk.op.assoc == Operator.Assoc.Unary && t.op.assoc != Operator.Assoc.Unary) break; // unaries can apply only unaries
2590 // do current operator
2591 doOperator(*t);
2592 --sp;
2595 if (isRParen) {
2596 privBooster -= 100;
2597 goto another_operator;
2600 if (sp >= stack.length) throw new CswErrorParser("expression too complex");
2601 stack[sp++] = tk;
2602 debug(CswParser) writeln("psh: ", tk);
2605 if (privBooster) throw new CswErrorParser("unmatched '('");
2606 debug(CswParser) writeln("doing operators");
2607 // done, now process all stacked operators
2608 foreach_reverse (ref tk; stack[0..sp]) doOperator(tk);
2609 if (qp != 1) throw new CswErrorParser("invalid expression");
2610 debug(CswParser) writeln("RESULT: ", queue[0]);
2611 return queue[0];
2615 // ////////////////////////////////////////////////////////////////////////// //
2616 // <required|weak|medium|strong[:weight]>
2617 // <w0,w1,w2[:weight]>
2618 // return true if strength was here, current token is ">"
2619 bool parseStrength (ref ParserData st, ref CswStrength str, ref CswNumber weight) {
2620 auto tk = st.peekToken();
2621 // strength?
2622 if (tk.type == Token.Type.LBrac || tk.type == Token.Type.Less) {
2623 tk = st.nextToken(); // read brc
2624 auto end = (tk.type == Token.Type.LBrac ? Token.Type.RBrac : Token.Type.Great);
2625 tk = st.nextToken();
2626 if (tk.type == Token.Type.Id) {
2627 // named
2628 switch (tk.s) {
2629 case "required": str = Csw.Required; break;
2630 case "weak": str = Csw.Weak; break;
2631 case "medium": str = Csw.Medium; break;
2632 case "strong": str = Csw.Strong; break;
2633 default: throw new CswErrorParser("invalid strength: '"~tk.s~"'");
2635 tk = st.nextToken();
2636 } else if (tk.type != end && tk.type != Token.Type.Colon) {
2637 // numeric
2638 CswNumber[3] nn = void;
2639 foreach (immutable idx; 0..nn.length) {
2640 if (!tk.isNumber) throw new CswErrorParser("strength number expected");
2641 nn[idx] = tk.n;
2642 tk = st.nextToken();
2643 if (idx != nn.length-1) {
2644 if (tk.type != Token.Type.Comma) throw new CswErrorParser("comma expected");
2645 tk = st.nextToken();
2648 str = Csw.Strength(nn[0], nn[1], nn[2]);
2650 // parse weight
2651 if (tk.type == Token.Type.Colon) {
2652 tk = st.nextToken();
2653 if (!tk.isNumber) throw new CswErrorParser("weight number expected");
2654 weight = tk.n;
2655 tk = st.nextToken();
2657 // check for closing bracket
2658 if (tk.type != end) throw new CswErrorParser(end == Token.Type.RBrac ? "']' expected" : "'>' expected");
2659 return true;
2660 } else {
2661 return false;
2666 // <required|weak|medium|strong[:weight]> expr eqop expr
2667 // <w0,w1,w2[:weight]> expr eqop expr
2668 // default <required>
2669 CswConstraint constraint (ref ParserData st) {
2670 CswStrength str = Csw.Required; // default strength
2671 CswNumber weight = 1.0; // default weight
2672 parseStrength(st, str, weight);
2673 // left part
2674 auto ex0 = expr(st);
2675 // operator
2676 auto tk = st.nextToken();
2677 if (tk.type == Token.Type.Eq || tk.type == Token.Type.EqEq) {
2678 // equation
2679 auto ex1 = expr(st);
2680 //tk = st.nextToken();
2681 //if (!tk.isEOF) throw new CswErrorParser("invalid constraint expression");
2682 return new CswLinearEquation(cast(CswLinearExpression)ex0, cast(CswLinearExpression)ex1, str, weight);
2683 } else if (tk.type == Token.Type.LEq || tk.type == Token.Type.GEq) {
2684 // inequation
2685 auto cop = (tk.type == Token.Type.LEq ? Csw.CompOp.LEQ : Csw.CompOp.GEQ);
2686 auto ex1 = expr(st);
2687 //tk = st.nextToken();
2688 //if (!tk.isEOF) throw new CswErrorParser("invalid constraint expression");
2689 return new CswLinearInequality(cast(CswLinearExpression)ex0, cop, cast(CswLinearExpression)ex1, str, weight);
2690 } else {
2691 throw new CswErrorParser("invalid constraint expression");
2693 assert(0);
2697 // ////////////////////////////////////////////////////////////////////////// //
2698 public CswConstraint CswParseConstraint (string s, CswSimplexSolver solver) {
2699 try {
2700 auto st = ParserData(s, solver, ParserData.ExprMode.Constraint);
2701 auto res = constraint(st);
2702 if (!st.peekToken().isEOF) throw new CswErrorParser("invalid constraint expression");
2703 return res;
2704 } catch (CswErrorParser e) {
2705 debug { import std.stdio; stderr.writeln("PARSE ERROR IN: '", s, "'"); }
2706 throw e;
2708 assert(0);
2712 // ////////////////////////////////////////////////////////////////////////// //
2713 public CswNumber CswParseSimpleMath (string s, CswSimplexSolver solver) {
2714 try {
2715 auto st = ParserData(s, solver, ParserData.ExprMode.SimpleMath);
2716 auto ex = expr(st);
2717 if (!st.peekToken().isEOF) throw new CswErrorParser("invalid simple math expression");
2718 if (!ex.isNumber) throw new CswErrorParser("invalid simple math expression");
2719 return ex.n;
2720 } catch (CswErrorParser e) {
2721 debug { import std.stdio; stderr.writeln("PARSE ERROR (", e.msg, ") IN: '", s, "'"); }
2722 throw e;
2724 assert(0);
2728 // ////////////////////////////////////////////////////////////////////////// //
2729 public void CswParseScript (string s, CswSimplexSolver solver) {
2731 //TODO: don't duplicate stays (?)
2732 // var[(stay|normal)] varlist ;
2733 // varlist: vardef, varlist | vardef | {empty}
2734 // vardef: [<stay|normal>] varname[=simplemath]
2735 static void processVar (ref ParserData st) {
2736 debug(CswScript) writeln("var-start");
2737 bool isAllStay = false;
2738 auto tk = st.peekToken();
2739 // strength
2740 if (tk.type == Token.Type.LParen) {
2741 // this must be 'stay'
2742 st.nextToken(); // skip LParen
2743 tk = st.nextToken();
2744 if (!tk.isId || (tk.s != "stay" && tk.s != "normal")) throw new CswErrorParser("'stay' expected");
2745 isAllStay = (tk.s == "stay");
2746 tk = st.nextToken();
2747 if (tk.type != Token.Type.RParen) throw new CswErrorParser("')' expected");
2748 tk = st.peekToken();
2750 // varlist
2751 while (!tk.isEOX) {
2752 CswStrength str = Csw.Weak; // default strength
2753 CswNumber weight = 1.0; // default weight
2754 bool isStay = isAllStay;
2755 tk = st.nextToken(); // Id or Less, skipped
2756 // '<stay>'?
2757 if (tk.type == Token.Type.Less) {
2758 tk = st.nextToken(); // Id
2759 if (!tk.isId || (tk.s != "stay" && tk.s != "normal")) throw new CswErrorParser("'stay' expected");
2760 isStay = (tk.s == "stay");
2761 // '['?
2762 if (st.peekToken().type == Token.Type.LBrac) parseStrength(st, str, weight);
2763 tk = st.nextToken(); // Great
2764 if (tk.type != Token.Type.Great) throw new CswErrorParser("'>' expected");
2765 tk = st.nextToken(); // Id
2766 debug(CswScript) writeln(" isStay is ", isStay);
2768 if (!tk.isId) throw new CswErrorParser("identifier expected");
2769 auto varname = tk.s;
2770 CswNumber varvalue = 0.0; // default variable value is zero
2771 debug(CswScript) writeln(" varname is ", varname);
2772 if (st.peekToken().type == Token.Type.Eq) {
2773 // do initializer
2774 st.nextToken(); // skip '='
2775 st.mode = ParserData.ExprMode.SimpleMath;
2776 auto ex = expr(st);
2777 if (!ex.isNumber) throw new CswErrorParser("invalid variable init expression");
2778 varvalue = ex.n;
2780 debug(CswScript) writeln("var '", varname, "' = ", varvalue, " stay=", isStay);
2781 st.solver.registerVariable(varname, varvalue);
2782 if (isStay) st.solver.addStay(varname, str, weight);
2783 tk = st.peekToken();
2784 debug(CswScript) writeln("tk: ", tk, "; isEOX: ", tk.isEOX);
2785 // comma or EOX
2786 if (!tk.isEOX) {
2787 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2788 tk = st.nextToken(); // skip comma
2791 debug(CswScript) writeln("var-end");
2792 //debug(CswScript) st.solver.dumpVars();
2795 // define name = {tokens};
2796 static void processDefine (ref ParserData st) {
2797 debug(CswScript) writeln("define-start");
2798 while (!st.peekToken().isEOX) {
2799 auto tk = st.nextToken(); // Id
2800 if (!tk.isId) throw new CswErrorParser("identifier expected");
2801 auto defname = tk.s;
2802 tk = st.nextToken(); // should be '='
2803 if (tk.type != Token.Type.Eq) throw new CswErrorParser("'=' expected");
2804 // now cheat: remember current string and start skipping tokens
2805 auto saveds = st.sstr; // we'll need this
2806 tk = st.peekToken();
2807 while (!tk.isEOX && tk.type != Token.Type.Comma) {
2808 st.nextToken(); // skip this token
2809 tk = st.peekToken();
2811 // now cut skipped part of the string and strip spaces
2812 import std.string : strip;
2813 saveds = saveds[0..$-st.sstr.length].strip;
2814 while (saveds.length > 0 && (saveds[$-1] == ';' || saveds[$-1] == ',')) saveds = saveds[0..$-1];
2815 if (saveds.length == 0) throw new CswErrorParser("empty defines are not allowed");
2816 debug(CswScript) writeln("name='", defname, "'; value='", saveds, "'");
2817 st.solver.setDefine(defname, saveds);
2818 if (!tk.isEOX) {
2819 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2820 tk = st.nextToken(); // skip comma
2823 debug(CswScript) writeln("define-end");
2826 static void processUndefine (ref ParserData st) {
2827 st.nextToken(); // eat keyword
2828 assert(0);
2831 static void processPrint (ref ParserData st) {
2832 debug(CswScript) writeln("print-start");
2833 while (!st.peekToken().isEOX) {
2834 auto tk = st.nextToken(); // eat Id
2835 if (!tk.isId) throw new CswErrorParser("identifier expected");
2836 if (!st.solver.hasVariable(tk.s)) {
2837 import std.stdio;
2838 writeln("*** UNKNOWN VARIABLE: '", tk.s, "'");
2839 } else {
2840 import std.stdio;
2841 writeln(st.solver[tk.s]);
2843 tk = st.peekToken();
2844 if (!tk.isEOX) {
2845 if (tk.type != Token.Type.Comma) throw new CswErrorParser("',' expected");
2846 st.nextToken(); // skip comma
2849 debug(CswScript) writeln("print-end");
2852 static void processConstraint (ref ParserData st) {
2853 debug(CswScript) writeln("constraint-start");
2854 st.mode = ParserData.ExprMode.Constraint;
2855 auto cs = constraint(st);
2856 if (!st.peekToken().isEOX) throw new CswErrorParser("';' expected");
2857 debug(CswScript) writeln("constraint: ", cs);
2858 st.solver.addConstraint(cs);
2859 debug(CswScript) writeln("constraint-start");
2862 auto st = ParserData(s, solver); // mode is irrelevant here
2863 try {
2864 auto tk = st.nextToken();
2865 while (!tk.isEOF) {
2866 if (!tk.isEOX) {
2867 // check for keywords
2868 if (tk.isId) {
2869 debug(CswScript) writeln("ID: ", tk.s);
2870 switch (tk.s) {
2871 case "var": processVar(st); break;
2872 case "define": processDefine(st); break;
2873 case "undefine": processUndefine(st); break;
2874 case "print": processPrint(st); break;
2875 default: st.ungetToken(tk); processConstraint(st); break;
2877 } else {
2878 st.ungetToken(tk);
2879 processConstraint(st);
2881 if (!st.peekToken().isEOX) throw new CswErrorParser("invalid script expression");
2883 // skip semicolong
2884 while (st.peekToken().type == Token.Type.Semicolon) st.nextToken();
2885 tk = st.nextToken();
2887 } catch (CswErrorParser e) {
2888 debug {
2889 import std.stdio;
2890 stderr.writeln("PARSE ERROR IN SCRIPT: ", e.msg);
2891 stderr.writeln("POSITION: ", st.lastTokenPos);
2893 //writeln(s[0..st.lastTokenPos]);
2894 //writeln(s[0..st.pos]);
2895 throw e;