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