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*/;
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
56 private import std
.traits
: isSomeString
;
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 {
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 // ////////////////////////////////////////////////////////////////////////// //
118 class CswConstraint
{
120 static uint mCstIndex
;
122 @property static uint newIndex () @trusted nothrow @nogc {
123 if (++mCstIndex
== 0) assert(0, "out of constraint indexes");
128 CswStrength mStrength
= void;
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;
143 this (in CswStrength strength
=Csw
.Required
, CswNumber weight
=1.0) {
145 mStrength
= strength
;
149 abstract @property CswLinearExpression
expression ();
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; }
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
;
173 // add missing bracket -> see CswConstraint#ToString(...)
174 override string
toString () const { return super.toString()~")"; }
175 override @property string
expressionStr () const { return mExpression
.toString
; }
179 this (CswVariable var
, in CswStrength strength
=Csw
.Required
, CswNumber weight
=1.0) {
180 super(strength
, weight
);
182 mExpression
= new CswLinearExpression(mVariable
, -1.0, mVariable
.value
);
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(); }
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
{
202 CswLinearExpression mExpression
;
205 override @property string
expressionStr () const { return mExpression
.toString
; }
209 this (CswLinearExpression cle
, in CswStrength strength
=Csw
.Required
, CswNumber weight
=1.0) {
210 super(strength
, weight
);
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(); }
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)"; }
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);
242 mixin(buildCtor
!("CswAbstractVariable clv, CswNumber val", q
{
243 super(new CswLinearExpression(val
), strength
, weight
);
244 mExpression
.addVariable(clv
, -1.0);
247 mixin(buildCtor
!("CswLinearExpression cle, CswAbstractVariable clv", q
{
248 super(cast(CswLinearExpression
)cle
.clone(), strength
, weight
);
249 mExpression
.addVariable(clv
, -1.0);
252 mixin(buildCtor
!("CswLinearExpression cle1, CswLinearExpression cle2", q
{
253 super(cast(CswLinearExpression
)cle1
.clone(), strength
, weight
);
254 mExpression
.addExpression(cle2
, -1.0);
257 mixin(buildCtor
!("CswAbstractVariable cle, CswAbstractVariable clv", q
{
258 this(new CswLinearExpression(cle
), clv
, strength
, weight
);
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) {`~
268 ` case Csw.CompOp.GEQ:`~
269 ` mExpression.multiplyMe(-1.0);`~
270 ` mExpression.`~adder
~`(`~opr
~`);`~
272 ` case Csw.CompOp.LEQ:`~
273 ` mExpression.`~adder
~`(`~opr
~`, -1.0);`~
276 ` throw new CswErrorInternalError("Invalid operator in CswLinearInequality constructor");`~
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",
286 `super(new CswLinearExpression(clv2), strength, weight);`));
288 mixin(buildCtor
!("CswVariable clv, Csw.CompOp op, CswNumber val",
290 `super(new CswLinearExpression(val), strength, weight);`));
292 mixin(buildCtor
!("CswLinearExpression cle1, Csw.CompOp op, CswLinearExpression cle2",
294 `super(cast(CswLinearExpression)cle2.clone(), strength, weight);`,
297 mixin(buildCtor
!("CswAbstractVariable clv, Csw.CompOp op, CswLinearExpression cle",
299 `super(cast(CswLinearExpression)cle.clone(), strength, weight);`));
301 mixin(buildCtor
!("CswLinearExpression cle, Csw.CompOp op, CswAbstractVariable 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 // ////////////////////////////////////////////////////////////////////////// //
313 class CswLinearExpression
{
318 CswAbstractVariable var
;
322 Term
[uint] mTerms
; // from CswVariable to CswNumber, key is `vindex`
325 /// Create 'semi-valid' zero constant
326 this () @safe nothrow { this(0.0); }
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 %-(
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 {
355 foreach (ref cld; mTerms
.byValue
) cld.num
*= x
;
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");
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
) {
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
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
427 foreach (ref clv
; expr
.mTerms
.byValue
) addVariable(clv
.var
, clv
.num
*n
, subject
, solver
);
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
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
);
449 coeff
.num
= newCoefficient
;
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
);
460 final CswLinearExpression
setVariable (CswAbstractVariable v
, CswNumber c
) nothrow {
463 if (auto tt
= v
.vindex
in mTerms
) {
466 mTerms
[v
.vindex
] = Term(v
, c
);
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 () {
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
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.
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
);
505 dOldCoeff
.num
= newCoeff
;
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
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
) {
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.
546 /// Suppose this expression is
547 /// c + a*subject + a1*v1 + ... + an*vn
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
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
);
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
;
590 if (!Csw
.approx(mConstant
, 0.0) || mTerms
.length
== 0) return to
!string(mConstant
);
592 foreach (/*auto*/ clv
; mTerms
.byValue
) {
593 import std
.string
: format
;
594 s
~= format((first ?
"%s*%s" : " + %s*%s"), clv
.num
, clv
.var
);
600 // required for parser
601 static CswLinearExpression
doMath (dchar op
, CswLinearExpression e1
, CswLinearExpression e2
) {
602 //import csw.errors : CswErrorInvalidMathOp;
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 // ////////////////////////////////////////////////////////////////////////// //
616 private class CswTableau
{
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
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
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>
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
);
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
);
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
;
707 //CswAbstractVariable[CswAbstractVariable] rs;
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);
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
);
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 {
798 auto res
= v
.vindex
in mRows
;
799 return (res ? res
.expr
: null);
804 // ////////////////////////////////////////////////////////////////////////// //
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
{
815 CswSlackVariable mSVEditPlus
;
816 CswSlackVariable mSVEditMinus
;
817 CswNumber mPrevEditConstant
;
824 this (CswConstraint cn
, CswSlackVariable eplus
, CswSlackVariable eminus
, CswNumber prevEditConstant
, usize i
) {
827 mSVEditMinus
= eminus
;
828 mPrevEditConstant
= prevEditConstant
;
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
{
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.
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.
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.)
884 CswAbstractVariable var
;
888 EVM
[uint] mEditVarMap
; // key is vindex
891 uint mArtificialCounter
;
894 CswNumber
[2] mResolvePair
;
898 bool mOptimizeAutomatically
;
901 usize
[] mStackEdCns
; // stack
903 CswVariable
[string
] mVarMap
;
904 string
[string
] mDefineMap
; // TODO: defines with args
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");
916 mArtificialCounter
= 0;
920 mOptimizeAutomatically
= true;
921 mNeedsSolving
= false;
923 CswLinearExpression e
= new CswLinearExpression();
924 mRows
[mObjective
.vindex
] = Row(mObjective
, e
);
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
);
948 /// Add a constraint to the solver.
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;
959 cAddedOkDirectly
= tryAddingDirectly(expr
);
960 if (!cAddedOkDirectly
) {
961 // could not add directly
962 addWithArtificialVariable(expr
);
964 } catch (CswErrorRequiredFailure rf
) {
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();
985 CswSimplexSolver
addConstraint (string s
) { return addConstraint(CswParseConstraint(s
, this)); }
987 CswSimplexSolver
registerVariable (CswVariable var
) nothrow {
988 mVarMap
[var
.name
] = var
;
992 CswSimplexSolver
registerVariable (string name
, CswNumber value
) nothrow {
993 mVarMap
[name
] = new CswVariable(name
, value
);
997 debug package void dumpVars () const {
999 writeln("=== VARS ===");
1000 foreach (/*auto*/ v
; mVarMap
) writeln(" ", v
);
1001 writeln("============");
1004 /// Same as AddConstraint, throws no exceptions.
1007 /// false if the constraint resulted in an unsolvable system, otherwise true.
1008 bool addConstraintNoException (CswConstraint cn
) nothrow {
1012 } catch (CswErrorRequiredFailure
) {
1014 } catch (Exception
) {
1019 /// Add an edit constraint for a variable with a given strength.
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
) {
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.
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
);
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
;
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");
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
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.
1082 /// n = Number of edit variables to keep.
1083 CswSimplexSolver
removeEditVarsTo (int n
) {
1085 // using '.keys', 'cause mEditVarMap can be modified inside loop
1086 foreach (/*auto*/ v
; mEditVarMap
.values
) {
1087 //CswEditInfo cei = mEditVarMap[v.var.vindex].edit;
1089 if (cei
.index
>= n
) removeEditVar(v
.var
);
1091 assert(mEditVarMap
.length
== n
, "mEditVarMap.length != n");
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..
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
);
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
) {
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
);
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
);
1158 zRow
.addVariable(clv
, -cn
.weight
*cn
.strength
, mObjective
, this);
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
) {
1179 mMarkerVars
.remove(cn
.cindex
);
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
);
1194 CswNumber r
= -expr
.constant
/coeff
;
1195 if (exitVar
is null || r
< minRatio
) {
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
) {
1217 if (exitVar
is null) {
1218 // exitVar is still null
1219 if (col
.set
.length
== 0) {
1220 removeColumn(marker
);
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
) {
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) {
1262 //FIXME: mErrorVars.remove(eVars);
1263 mErrorVars
.remove(cn
.cindex
);
1267 if (mOptimizeAutomatically
) {
1268 optimize(mObjective
);
1269 setExternalVariables();
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
1284 void resolve (CswNumber
[] newEditConstants
) {
1285 foreach (ref ev
; mEditVarMap
.byValue
) {
1286 //CswEditInfo cei = mEditVarMap[v];
1289 immutable i
= cei
.index
;
1291 if (i
< newEditConstants
.length
) suggestValue(v
, newEditConstants
[i
]);
1292 } catch (CswError
) {
1293 //import csw.errors : CswErrorInternalError;
1294 throw new CswErrorInternalError("Error during 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).
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
);
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();
1357 CswSimplexSolver
setEditedValue (CswVariable v
, CswNumber n
) {
1358 if (!containsVariable(v
)) {
1362 if (!Csw
.approx(n
, v
.value
)) {
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");
1377 bool containsVariable (CswVariable v
) nothrow { return columnsHasKey(v
) ||
(rowExpression(v
) !is null); }
1379 CswSimplexSolver
addVar (CswVariable v
) {
1380 if (!containsVariable(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");
1392 /// Returns information about the solver's internals.
1394 /// Originally from Michael Noth <noth@cs.washington.edu>
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
);
1411 string
getDebugInfo () const {
1412 string result
= toString();
1413 result
~= getInternalInfo();
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
);
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();
1445 CswLinearExpression azTableauRow
= rowExpression(az
);
1446 if (!Csw
.approx(azTableauRow
.constant
, 0.0)) {
1449 throw new CswErrorRequiredFailure("!!!");
1452 // see if av is a basic variable
1453 CswLinearExpression e
= rowExpression(av
);
1456 // find another variable in this row and pivot,
1457 // so that av becomes parametric
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
1466 CswAbstractVariable entryVar
= e
.anyPivotableVariable();
1467 pivot(entryVar
, av
);
1469 assert(rowExpression(av
) is null, "rowExpression(av) == null)");
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
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];
1514 immutable c
= clv
.num
;
1515 if (foundUnrestricted
) {
1516 if (!v
.isRestricted
) {
1517 if (!columnsHasKey(v
)) return v
;
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
))) {
1526 foundNewRestricted
= true;
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];
1541 immutable c
= clv
.num
;
1542 if (!v
.isDummy
) return null; // nope, no luck
1543 if (!columnsHasKey(v
)) {
1549 if (!Csw
.approx(expr
.constant
, 0.0)) throw new CswErrorRequiredFailure("!!!");
1550 if (coeff
> 0.0) expr
.multiplyMe(-1);
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];
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
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).
1591 slackVar
= new CswSlackVariable(mSlackCounter
, "s");
1592 expr
.setVariable(slackVar
, -1);
1593 mMarkerVars
[cn
.cindex
] = MKV(cn
, slackVar
);
1594 if (!cn
.isRequired
) {
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
);
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.
1609 dummyVar
= new CswDummyVariable(mDummyCounter
, "d");
1610 expr
.setVariable(dummyVar
, 1.0);
1611 mMarkerVars
[cn
.cindex
] = MKV(cn
, dummyVar
);
1613 // cn is a non-required equality. Add a positive and a negative error
1614 // variable, making the resulting constraint
1615 // expr = eplus - eminus
1617 // expr - eplus + eminus = 0
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);
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;
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
1663 foreach (ref clv
; zRow
.mTerms
.byValue
) {
1664 //CswNumber c = terms[v];
1666 immutable c
= clv
.num
;
1667 if (v
.isPivotable
&& c
< objectiveCoeff
) {
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
];
1679 foreach (/*auto*/ v
; columnVars
.set
) {
1680 if (v
.isPivotable
) {
1681 CswLinearExpression expr
= rowExpression(v
);
1682 CswNumber coeff
= expr
.coefficientFor(entryVar
);
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()))
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
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
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
;
1737 CswLinearExpression exprMinus
= rowExpression(minusErrorVar
);
1738 if (exprMinus
!is null) {
1739 exprMinus
.incrementConstant(-delta
);
1740 if (exprMinus
.constant
< 0.0) mInfeasibleRows
[minusErrorVar
.vindex
] = minusErrorVar
;
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
) {
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
;
1769 //auto terms = expr.terms;
1770 foreach (ref clv
; expr
.mTerms
.byValue
) {
1771 //CswNumber c = terms[v];
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
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:
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
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
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"); }
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
;
1869 //CswAbstractVariable[CswAbstractVariable] ev;
1872 ev
.vars
[var
.vindex
] = var
;
1873 mErrorVars
[cn
.cindex
] = ev
;
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 // ////////////////////////////////////////////////////////////////////////// //
1885 class CswAbstractVariable
{
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
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
); }
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]"; }
1920 static string
buildIndexedName (string pfx
, uint idx
, uint num
=uint.max
) @trusted nothrow {
1922 usize pos
= n
.length
;
1924 if (num
!= uint.max
) {
1926 n
[--pos
] = num
%10+'0';
1933 n
[--pos
] = idx
%10+'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
)
1949 enum HasStr
= HasStr
!(s
, T
[1..$]);
1952 template IFS (bool v
, string t
="true", string f
="false") {
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
);
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]"; }
2000 class CswVariable
: CswAbstractVariable
{
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*/ {
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); }
2044 template buildOpBinConstraint(string ops
) {
2045 static if (ops
.length
> 1)
2046 enum buildOpBinConstraint
= `op == "`~ops
[0]~`" || `~buildOpBinConstraint
!(ops
[1..$]);
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;");`~
2060 // ////////////////////////////////////////////////////////////////////////// //
2063 debug import std
.stdio
;
2064 debug(CswParser
) import std
.stdio
;
2065 debug(CswTokenizer
) import std
.stdio
;
2068 // ////////////////////////////////////////////////////////////////////////// //
2070 static immutable string oneChars
= "=+-*/()[]<>,;:"; // WARNING! keep in sync with Type enum!
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
;
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
;
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 "<=";
2101 if (type
>= Type
.Eq
&& type
<= Type
.max
) return to
!string(oneChars
[type
-Type
.Eq
]);
2108 void skipBlanks (ref ParserData st
) {
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?
2116 ch
= st
.getChar(); // skip first slash
2118 // multiline comment
2124 if (ch
== '/') break;
2131 // alas, unget 'em both
2136 ch
= '#'; // comment-to-eol
2140 do { ch
= st
.getChar(); } while (ch
!= 0 && ch
!= '\n');
2143 // other non-white and non-comment chars
2150 Token
getToken (ref ParserData st
) {
2151 static bool isId (dchar ch
) {
2152 import std
.uni
: isAlpha
;
2154 (ch
>= '0' && ch
<= '9') ||
2155 (ch
>= 'A' && ch
<= 'Z') ||
2156 (ch
>= 'a' && ch
<= 'z') ||
2157 ch
== '_' || ch
== '.' ||
2164 st
.lastTokenPos
= st
.pos
;
2166 debug(CswTokenizer
) writeln("lastTokenPos=", st
.lastTokenPos
, "; ch=", ch
);
2167 if (ch
== 0) return Token(st
.lastTokenPos
, Token
.Type
.EOF
);
2170 if (ch
== '<' || ch
== '>' || ch
== '=') {
2171 dchar ch1
= st
.getChar();
2172 debug(CswTokenizer
) writeln(" ?2char; ch=", ch
, "; ch1=", ch1
);
2174 Token
.Type tt
= void;
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
);
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
));
2195 if (ch
>= '0' && ch
<= '9') {
2197 while (ch
>= '0' && ch
<= '9') {
2202 CswNumber frc
= 0.1;
2204 while (ch
>= '0' && ch
<= '9') {
2211 debug(CswTokenizer
) writeln(" number=", n
);
2212 return Token(st
.lastTokenPos
, Token
.Type
.Number
, n
);
2216 if (ch
!= '.' && isId(ch
)) {
2217 import std
.array
: appender
;
2218 auto id
= appender
!string();
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");
2233 // ////////////////////////////////////////////////////////////////////////// //
2235 enum Assoc
{ None
, Left
, Right
, Unary
}
2237 Token
.Type ttype
= Token
.Type
.EOF
;
2238 Assoc assoc
= Assoc
.None
;
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
)~")";
2257 enum Type
{ EOF
, Number
, Var
, Expr
, Operator
}
2259 Type type
= Type
.EOF
;
2262 CswLinearExpression e
;
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
) () {
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 // ////////////////////////////////////////////////////////////////////////// //
2314 enum ExprMode
{ Constraint
, SimpleMath
}
2317 CswSimplexSolver solver
;
2318 ExprMode mode
= ExprMode
.Constraint
;
2319 usize pos
; // current stream position
2326 void ungetChar (dchar ch
) {
2327 if (savedChCount
== savedCh
.length
) throw new CswErrorParser("too many ungetChar()s");
2329 import std
.utf
: codeLength
;
2330 usize clen
= codeLength
!dchar(ch
);
2331 if (pos
> clen
) pos
-= clen
; else pos
= 0;
2332 savedCh
[savedChCount
++] = ch
;
2340 import std
.utf
: codeLength
;
2341 ch
= savedCh
[--savedChCount
];
2342 clen
= codeLength
!dchar(ch
);
2344 import std
.utf
: decodeFront
;
2345 if (sstr
.length
== 0) return 0; // 0 means EOF
2346 ch
= sstr
.decodeFront(clen
);
2349 if (ch
== 0) ch
= ' ';
2353 //FIXME: make this faster!
2354 void prependString (string s
) {
2356 while (savedChCount
) {
2357 import std
.conv
: to
;
2358 sstr
= to
!string(savedCh
[--savedChCount
])~sstr
;
2364 Token
nextToken () {
2369 return getToken(this);
2372 Token
peekToken () {
2374 tk
= getToken(this);
2380 void ungetToken (Token atk
) {
2381 if (atk
.isEOF
) return;
2382 if (tkValid
) throw new CswErrorParser("internal ungetToken error");
2389 // ////////////////////////////////////////////////////////////////////////// //
2390 Term
term (ref ParserData st
) {
2392 auto tk
= st
.nextToken();
2397 if (tk
.isNumber
) return Term(tk
.n
);
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
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
);
2416 debug(CswParser
) { errwriteln("var not found: '", tk
.s
, "'"); }
2417 throw new CswErrorParser("var not found: '"~tk
.s
~"'");
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
) {
2426 debug(CswParser
) writeln(" GOT PUNCT!");
2432 //debug(CswParser) writeln("rest: '", st.sstr, "'");
2433 debug(CswParser
) writeln("tk: '", 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
;
2455 auto eres
= (cast(CswLinearExpression
)queue
[qp
-1])*(-1.0);
2456 queue
[qp
-1] = Term(eres
);
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;
2475 // if one of the operans is number (0.0 or 1.0), do a little optimisation too
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
== '-') {
2483 auto eres
= (cast(CswLinearExpression
)queue
[qp
-1])*(-1.0);
2484 queue
[qp
-2] = Term(eres
);
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
== '/') {
2492 queue
[qp
-2] = queue
[qp
-1];
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
== '*') {
2504 queue
[qp
-2] = queue
[qp
-1];
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
);
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
) {
2533 if (sp
> 0 && stack
[sp
-1].isOperator
&& stack
[sp
-1].op
.ttype
== Token
.Type
.Minus
) {
2534 // two unary minuses gives no-op
2538 if (sp
>= stack
.length
) throw new CswErrorParser("expression too complex");
2539 stack
[sp
++] = Term(opUnaryMinus
);
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
2548 throw new CswErrorParser("invalid expression");
2550 // numbers, vars and exprs are ok here (actually, there can be no expr)
2553 // put argument to queue
2554 if (qp
>= queue
.length
) throw new CswErrorParser("expression too complex");
2557 // now process operators
2561 debug(CswParser
) writeln("opr: ", tk
);
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) {
2574 tk
.op
.prio
+= privBooster
;
2575 } else if (isRParen
) {
2577 if (privBooster
< 100) throw new CswErrorParser("unmatched ')'");
2578 tk
.op
.prio
= privBooster
;
2581 // process operator stack
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
2597 goto another_operator
;
2600 if (sp
>= stack
.length
) throw new CswErrorParser("expression too complex");
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]);
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();
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
) {
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
) {
2638 CswNumber
[3] nn
= void;
2639 foreach (immutable idx
; 0..nn
.length
) {
2640 if (!tk
.isNumber
) throw new CswErrorParser("strength number expected");
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]);
2651 if (tk
.type
== Token
.Type
.Colon
) {
2652 tk
= st
.nextToken();
2653 if (!tk
.isNumber
) throw new CswErrorParser("weight number expected");
2655 tk
= st
.nextToken();
2657 // check for closing bracket
2658 if (tk
.type
!= end
) throw new CswErrorParser(end
== Token
.Type
.RBrac ?
"']' expected" : "'>' expected");
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
);
2674 auto ex0
= expr(st
);
2676 auto tk
= st
.nextToken();
2677 if (tk
.type
== Token
.Type
.Eq || tk
.type
== Token
.Type
.EqEq
) {
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
) {
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
);
2691 throw new CswErrorParser("invalid constraint expression");
2697 // ////////////////////////////////////////////////////////////////////////// //
2698 public CswConstraint
CswParseConstraint (string s
, CswSimplexSolver solver
) {
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");
2704 } catch (CswErrorParser e
) {
2705 debug { import std
.stdio
; stderr
.writeln("PARSE ERROR IN: '", s
, "'"); }
2712 // ////////////////////////////////////////////////////////////////////////// //
2713 public CswNumber
CswParseSimpleMath (string s
, CswSimplexSolver solver
) {
2715 auto st
= ParserData(s
, solver
, ParserData
.ExprMode
.SimpleMath
);
2717 if (!st
.peekToken().isEOF
) throw new CswErrorParser("invalid simple math expression");
2718 if (!ex
.isNumber
) throw new CswErrorParser("invalid simple math expression");
2720 } catch (CswErrorParser e
) {
2721 debug { import std
.stdio
; stderr
.writeln("PARSE ERROR (", e
.msg
, ") IN: '", s
, "'"); }
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();
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();
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
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");
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
) {
2774 st
.nextToken(); // skip '='
2775 st
.mode
= ParserData
.ExprMode
.SimpleMath
;
2777 if (!ex
.isNumber
) throw new CswErrorParser("invalid variable init expression");
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
);
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
);
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
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
)) {
2838 writeln("*** UNKNOWN VARIABLE: '", tk
.s
, "'");
2841 writeln(st
.solver
[tk
.s
]);
2843 tk
= st
.peekToken();
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
2864 auto tk
= st
.nextToken();
2867 // check for keywords
2869 debug(CswScript
) writeln("ID: ", 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;
2879 processConstraint(st
);
2881 if (!st
.peekToken().isEOX
) throw new CswErrorParser("invalid script expression");
2884 while (st
.peekToken().type
== Token
.Type
.Semicolon
) st
.nextToken();
2885 tk
= st
.nextToken();
2887 } catch (CswErrorParser e
) {
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]);