1 module cassowary.SimplexSolver; 2 3 import std.array; 4 import std.conv; 5 import std.stdio; 6 import std.math; 7 8 import cassowary.Tableau; 9 import cassowary.AbstractVariable; 10 import cassowary.Constraint; 11 import cassowary.Variable; 12 import cassowary.Strength; 13 import cassowary.Point; 14 import cassowary.LinearExpression; 15 import cassowary.LinearInequality; 16 import cassowary.ObjectiveVariable; 17 import cassowary.set; 18 import cassowary.EditInfo; 19 import cassowary.Cl; 20 import cassowary.Error; 21 import cassowary.EditConstraint; 22 import cassowary.StayConstraint; 23 import cassowary.SlackVariable; 24 import cassowary.DummyVariable; 25 import cassowary.SymbolicWeight; 26 27 28 class ClSimplexSolver : ClTableau 29 { 30 // Ctr initializes the fields, and creates the objective row 31 this() 32 { 33 _resolve_pair = [0, 0]; 34 _objective = new ClObjectiveVariable("Z"); 35 36 auto e = new ClLinearExpression(); 37 _rows[_objective] = e; 38 _stkCedcns = [0]; 39 40 traceprint("objective expr == " ~ rowExpression(_objective).toString()); 41 } 42 43 // Convenience function for creating a linear inequality constraint 44 final ClSimplexSolver addLowerBound(ClAbstractVariable v, double lower) 45 { 46 auto cn = new ClLinearInequality(v, InequalityType.GEQ, new ClLinearExpression(lower)); 47 return addConstraint(cn); 48 } 49 50 // Convenience function for creating a linear inequality constraint 51 final ClSimplexSolver addUpperBound(ClAbstractVariable v, double upper) 52 { 53 auto cn = new ClLinearInequality(v, InequalityType.LEQ, new ClLinearExpression(upper)); 54 return addConstraint(cn); 55 } 56 57 // Convenience function for creating a pair of linear inequality constraint 58 final ClSimplexSolver addBounds(ClAbstractVariable v, double lower, double upper) 59 { 60 addLowerBound(v, lower); addUpperBound(v, upper); return this; 61 } 62 63 // Add constraint "cn" to the solver 64 final ClSimplexSolver addConstraint(ClConstraint cn) 65 { 66 fnenterprint("addConstraint: " ~ cn.toString()); 67 68 ClAbstractVariable[] eplus_eminus; 69 double prevEConstant = 0; 70 ClLinearExpression expr = newExpression(cn, /* output to: */ 71 eplus_eminus, prevEConstant); 72 bool fAddedOkDirectly = false; 73 74 try 75 { 76 fAddedOkDirectly = tryAddingDirectly(expr); 77 if (!fAddedOkDirectly) 78 { 79 // could not add directly 80 addWithArtificialVariable(expr); 81 } 82 } 83 catch (ClErrorRequiredFailure err) 84 { 85 ///try { 86 /// removeConstraint(cn); // FIXGJB 87 // } catch (ClErrorConstraintNotFound errNF) { 88 // This should not possibly happen 89 /// System.err.println("ERROR: could not find a constraint just added\n"); 90 ///} 91 throw err; 92 } 93 94 _fNeedsSolving = true; 95 96 if (cn.isEditConstraint()) 97 { 98 auto i = _editVarMap.length; 99 ClEditConstraint cnEdit = cast(ClEditConstraint) cn; 100 ClSlackVariable clvEplus = cast(ClSlackVariable) eplus_eminus[0]; 101 ClSlackVariable clvEminus = cast(ClSlackVariable) eplus_eminus[1]; 102 103 _editVarMap[cnEdit.variable()] = new ClEditInfo(cnEdit, clvEplus, clvEminus, 104 prevEConstant, i); 105 } 106 107 if (autosolve) 108 { 109 optimize(_objective); 110 setExternalVariables(); 111 } 112 113 return this; 114 } 115 116 // Same as addConstraint, except returns false if the constraint 117 // resulted in an unsolvable system (instead of throwing an exception) 118 final bool addConstraintNoException(ClConstraint cn) 119 { 120 fnenterprint("addConstraintNoException: " ~ cn.toString()); 121 122 try 123 { 124 addConstraint(cn); 125 return true; 126 } 127 catch (ClErrorRequiredFailure e) 128 { 129 return false; 130 } 131 } 132 133 // Add an edit constraint for "v" with given strength 134 final ClSimplexSolver addEditVar(ClVariable v, const ClStrength strength = ClStrength.strong) 135 { 136 try 137 { 138 ClEditConstraint cnEdit = new ClEditConstraint(v, strength); 139 return addConstraint(cnEdit); 140 } 141 catch (ClErrorRequiredFailure e) 142 { 143 // should not get this 144 throw new ClErrorInternal("Required failure when adding an edit variable"); 145 } 146 } 147 148 // Remove the edit constraint previously added for variable v 149 final ClSimplexSolver removeEditVar(ClVariable v) 150 { 151 ClEditInfo cei = _editVarMap[v]; 152 ClConstraint cn = cei.Constraint(); 153 removeConstraint(cn); 154 return this; 155 } 156 157 // beginEdit() should be called before sending 158 // resolve() messages, after adding the appropriate edit variables 159 final ClSimplexSolver beginEdit() 160 { 161 assert(_editVarMap.length > 0, "_editVarMap.length() > 0"); 162 // may later want to do more in here 163 _infeasibleRows.clear(); 164 resetStayConstants(); 165 _stkCedcns ~= _editVarMap.length; 166 return this; 167 } 168 169 // endEdit should be called after editing has finished 170 // for now, it just removes all edit variables 171 final ClSimplexSolver endEdit() 172 { 173 assert(_editVarMap.length > 0, "_editVarMap.length() > 0"); 174 resolve(); 175 _stkCedcns.popBack(); 176 size_t n = _stkCedcns[$-1]; 177 removeEditVarsTo(n); 178 // may later want to do more in here 179 return this; 180 } 181 182 // removeAllEditVars() just eliminates all the edit constraints 183 // that were added 184 final ClSimplexSolver removeAllEditVars() 185 { 186 return removeEditVarsTo(0); 187 } 188 189 // remove the last added edit vars to leave only n edit vars left 190 final ClSimplexSolver removeEditVarsTo(size_t n) 191 { 192 try 193 { 194 foreach(ClVariable v, ClEditInfo cei; _editVarMap) 195 { 196 if (cei.Index() >= n) 197 { 198 removeEditVar(v); 199 } 200 } 201 assert(_editVarMap.length == n, "_editVarMap.length() == n"); 202 203 return this; 204 } 205 catch (ClErrorConstraintNotFound e) 206 { 207 // should not get this 208 throw new ClErrorInternal("Constraint not found in removeEditVarsTo"); 209 } 210 } 211 212 // Add weak stays to the x and y parts of each point. These have 213 // increasing weights so that the solver will try to satisfy the x 214 // and y stays on the same point, rather than the x stay on one and 215 // the y stay on another. 216 final ClSimplexSolver addPointStays(ClPoint[] listOfPoints) 217 { 218 fnenterprint("addPointStays" ~ listOfPoints.to!string()); 219 double weight = 1.0; 220 double multiplier = 2.0; 221 foreach(p; listOfPoints) 222 { 223 addPointStay(p, weight); 224 weight *= multiplier; 225 } 226 return this; 227 } 228 229 final ClSimplexSolver addPointStay(ClVariable vx, ClVariable vy, double weight = 1) 230 { 231 addStay(vx, ClStrength.weak, weight); 232 addStay(vy, ClStrength.weak, weight); 233 return this; 234 } 235 236 final ClSimplexSolver addPointStay(ClPoint clp, double weight = 1) 237 { 238 return addPointStay(clp.X(), clp.Y(), weight); 239 } 240 241 // Add a stay of the given strength (default to weak) of v to the tableau 242 final ClSimplexSolver addStay(ClVariable v, const ClStrength strength = ClStrength.weak, double weight = 1) 243 { 244 auto cn = new ClStayConstraint(v, strength, weight); 245 return addConstraint(cn); 246 } 247 248 // Remove the constraint cn from the tableau 249 // Also remove any error variable associated with cn 250 final ClSimplexSolver removeConstraint(ClConstraint cn) 251 { 252 fnenterprint("removeConstraint: " ~ cn.toString()); 253 traceprint(this.toString()); 254 255 _fNeedsSolving = true; 256 257 resetStayConstants(); 258 259 ClLinearExpression zRow = rowExpression(_objective); 260 261 auto eVars = _errorVars.get(cn, null); 262 traceprint("eVars == " ~ eVars.to!string()); 263 264 if (eVars !is null) 265 { 266 foreach(ClAbstractVariable clv; eVars) 267 { 268 ClLinearExpression expr = rowExpression(clv); 269 if (expr is null ) 270 { 271 zRow.addVariable(clv, -cn.weight() * 272 cn.strength().symbolicWeight.asDouble(), 273 _objective, this); 274 } 275 else // the error variable was in the basis 276 { 277 zRow.addExpression(expr, -cn.weight() * 278 cn.strength().symbolicWeight.asDouble(), 279 _objective, this); 280 } 281 } 282 } 283 284 ClAbstractVariable marker = _markerVars.get(cn, null); 285 286 if (marker is null) 287 { 288 throw new ClErrorConstraintNotFound(); 289 } 290 291 _markerVars.remove(cn); 292 293 traceprint("Looking to remove var " ~ marker.toString()); 294 295 if (rowExpression(marker) is null ) 296 { 297 // not in the basis, so need to do some work 298 auto col = _columns[marker]; 299 300 traceprint("Must pivot -- columns are " ~ col.toString()); 301 302 ClAbstractVariable exitVar = null; 303 double minRatio = 0.0; 304 foreach(ClAbstractVariable v; col) 305 { 306 if (v.isRestricted() ) 307 { 308 ClLinearExpression expr = rowExpression( v); 309 double coeff = expr.coefficientFor(marker); 310 traceprint("Marker " ~ marker.to!string() ~ "'s coefficient in " ~ expr.toString() ~ " is " ~ coeff.to!string()); 311 if (coeff < 0.0) 312 { 313 double r = -expr.constant() / coeff; 314 if (exitVar is null || r < minRatio) 315 { 316 minRatio = r; 317 exitVar = v; 318 } 319 } 320 } 321 } 322 if (exitVar is null ) 323 { 324 traceprint("exitVar is still null"); 325 foreach (ClAbstractVariable v; col) 326 { 327 if (v.isRestricted() ) 328 { 329 ClLinearExpression expr = rowExpression(v); 330 double coeff = expr.coefficientFor(marker); 331 double r = expr.constant() / coeff; 332 if (exitVar is null || r < minRatio) 333 { 334 minRatio = r; 335 exitVar = v; 336 } 337 } 338 } 339 } 340 341 if (exitVar is null) 342 { 343 // exitVar is still null 344 if (col.length == 0) 345 { 346 removeColumn(marker); 347 } 348 else 349 { 350 exitVar = col.anyElement; 351 } 352 } 353 354 if (exitVar !is null) 355 { 356 pivot(marker, exitVar); 357 } 358 } 359 360 if (rowExpression(marker) !is null ) 361 { 362 ClLinearExpression expr = removeRow(marker); 363 expr = null; 364 } 365 366 if (eVars !is null) 367 { 368 foreach(ClAbstractVariable v; eVars) 369 { 370 // FIXGJBNOW != or equals? 371 if ( v != marker ) 372 { 373 removeColumn(v); 374 v = null; 375 } 376 } 377 } 378 379 if (cn.isStayConstraint()) 380 { 381 if (eVars !is null) 382 { 383 for (int i = 0; i < _stayPlusErrorVars.length; i++) 384 { 385 eVars.remove(_stayPlusErrorVars[i]); 386 eVars.remove(_stayMinusErrorVars[i]); 387 } 388 } 389 } 390 else if (cn.isEditConstraint()) 391 { 392 assert(eVars !is null, "eVars != null"); 393 ClEditConstraint cnEdit = cast(ClEditConstraint) cn; 394 ClVariable clv = cnEdit.variable(); 395 ClEditInfo cei = _editVarMap[clv]; 396 ClSlackVariable clvEditMinus = cei.ClvEditMinus(); 397 // ClSlackVariable clvEditPlus = cei.ClvEditPlus(); 398 // the clvEditPlus is a marker variable that is removed elsewhere 399 removeColumn( clvEditMinus ); 400 _editVarMap.remove(clv); 401 } 402 403 // FIXGJB do the remove at top 404 if (eVars !is null) 405 { 406 _errorVars.remove(cn); 407 } 408 marker = null; 409 410 if (autosolve) 411 { 412 optimize(_objective); 413 setExternalVariables(); 414 } 415 416 return this; 417 } 418 419 // Re-initialize this solver from the original constraints, thus 420 // getting rid of any accumulated numerical problems. (Actually, we 421 // haven't definitely observed any such problems yet) 422 final void reset() 423 { 424 fnenterprint("reset"); 425 throw new ClErrorInternal("reset not implemented"); 426 } 427 428 // Re-solve the current collection of constraints for new values for 429 // the constants of the edit variables. 430 // DEPRECATED: use suggestValue(...) then resolve() 431 // If you must use this, be sure to not use it if you 432 // remove an edit variable (or edit constraint) from the middle 433 // of a list of edits and then try to resolve with this function 434 // (you'll get the wrong answer, because the indices will be wrong 435 // in the ClEditInfo objects) 436 final void resolve(double[] newEditConstants) 437 { 438 fnenterprint("resolve" ~ newEditConstants.to!string()); 439 foreach(ClVariable v, ClEditInfo cei; _editVarMap) 440 { 441 auto i = cei.Index(); 442 try 443 { 444 if (i < newEditConstants.length) 445 suggestValue(v, newEditConstants[i]); 446 } 447 catch (ClError err) 448 { 449 throw new ClErrorInternal("Error during resolve"); 450 } 451 } 452 resolve(); 453 } 454 455 // Convenience function for resolve-s of two variables 456 final void resolve(double x, double y) 457 { 458 _resolve_pair[0] = x; 459 _resolve_pair[1] = y; 460 resolve(_resolve_pair); 461 } 462 463 // Re-solve the cuurent collection of constraints, given the new 464 // values for the edit variables that have already been 465 // suggested (see suggestValue() method) 466 final void resolve() 467 { 468 fnenterprint("resolve()"); 469 dualOptimize(); 470 setExternalVariables(); 471 _infeasibleRows.clear(); 472 resetStayConstants(); 473 } 474 475 // Suggest a new value for an edit variable 476 // the variable needs to be added as an edit variable 477 // and beginEdit() needs to be called before this is called. 478 // The tableau will not be solved completely until 479 // after resolve() has been called 480 final ClSimplexSolver suggestValue(ClVariable v, double x) 481 { 482 fnenterprint("suggestValue(" ~ v.toString() ~ ", " ~ x.to!string() ~ ")"); 483 ClEditInfo cei = _editVarMap.get(v, null); 484 if (cei is null) 485 { 486 writeln("suggestValue for variable " ~ v.toString() ~ ", but var is not an edit variable\n"); 487 throw new ClError(); 488 } 489 490 ClSlackVariable clvEditPlus = cei.ClvEditPlus(); 491 ClSlackVariable clvEditMinus = cei.ClvEditMinus(); 492 double delta = x - cei.PrevEditConstant(); 493 cei.SetPrevEditConstant(x); 494 deltaEditConstant(delta, clvEditPlus, clvEditMinus); 495 return this; 496 } 497 498 // If autosolving has been turned off, client code needs 499 // to explicitly call solve() before accessing variables 500 // values 501 final ClSimplexSolver solve() 502 { 503 if (_fNeedsSolving) 504 { 505 optimize(_objective); 506 setExternalVariables(); 507 } 508 return this; 509 } 510 511 ClSimplexSolver setEditedValue(ClVariable v, double n) 512 { 513 if (!containsVariable(v)) 514 { 515 v.change_value(n); 516 return this; 517 } 518 519 if (!approxEqual(n, v.value())) 520 { 521 addEditVar(v); 522 beginEdit(); 523 try 524 { 525 suggestValue(v, n); 526 } 527 catch (ClError e) 528 { 529 // just added it above, so we shouldn't get an error 530 throw new ClErrorInternal("Error in setEditedValue"); 531 } 532 endEdit(); 533 } 534 return this; 535 } 536 537 final bool containsVariable(ClVariable v) 538 { 539 return columnsHasKey(v) || (rowExpression(v) !is null); 540 } 541 542 ClSimplexSolver addVar(ClVariable v) 543 { 544 if (!containsVariable(v)) 545 { 546 try 547 { 548 addStay(v); 549 } 550 catch (ClErrorRequiredFailure e) 551 { 552 // cannot have a required failure, since we add w/ weak 553 throw new ClErrorInternal("Error in addVar -- required failure is impossible"); 554 } 555 traceprint("added initial stay on " ~ v.toString()); 556 } 557 return this; 558 } 559 560 // Originally from Michael Noth <noth@cs> 561 override string getInternalInfo() 562 { 563 string res = super.getInternalInfo(); 564 res ~= "\nSolver info:\n"; 565 res ~= "Stay Error Variables: "; 566 res ~= (_stayPlusErrorVars.length - _stayMinusErrorVars.length).to!string(); 567 res ~= " (" ~ _stayPlusErrorVars.length.to!string() ~ " +, "; 568 res ~= _stayMinusErrorVars.length.to!string() ~ " -)\n"; 569 res ~= "Edit Variables: " ~ _editVarMap.length.to!string(); 570 res ~= "\n"; 571 return res; 572 } 573 574 final string getDebugInfo() 575 { 576 string res = toString(); 577 res ~= getInternalInfo(); 578 return res; 579 } 580 581 override string toString() const 582 { 583 string res = super.toString(); 584 res ~= "\n_stayPlusErrorVars: "; 585 res ~= _stayPlusErrorVars.to!string(); 586 res ~= "\n_stayMinusErrorVars: "; 587 res ~= _stayMinusErrorVars.to!string(); 588 589 res ~= "\n"; 590 return res; 591 } 592 593 auto getConstraintMap() 594 { 595 return _markerVars; 596 } 597 598 //// END PUBLIC INTERFACE 599 600 // Add the constraint expr=0 to the inequality tableau using an 601 // artificial variable. To do this, create an artificial variable 602 // av and add av=expr to the inequality tableau, then make av be 0. 603 // (Raise an exception if we can't attain av=0.) 604 protected final void addWithArtificialVariable(ClLinearExpression expr) 605 { 606 fnenterprint("addWithArtificialVariable: " ~ expr.toString()); 607 608 ClSlackVariable av = new ClSlackVariable(++_artificialCounter, "a"); 609 ClObjectiveVariable az = new ClObjectiveVariable("az"); 610 ClLinearExpression azRow = cast(ClLinearExpression) expr.clone(); 611 612 traceprint("before addRows:\n" ~ toString()); 613 614 addRow(az, azRow); 615 addRow(av, expr); 616 617 traceprint("after addRows:\n" ~ toString()); 618 optimize(az); 619 620 ClLinearExpression azTableauRow = rowExpression(az); 621 622 traceprint("azTableauRow.constant() == " ~ azTableauRow.constant().to!string()); 623 624 if (!approxEqual(azTableauRow.constant(), 0.0)) 625 { 626 removeRow(az); 627 removeColumn(av); 628 throw new ClErrorRequiredFailure(); 629 } 630 631 // See if av is a basic variable 632 ClLinearExpression e = rowExpression(av); 633 634 if (e !is null) 635 { 636 // find another variable in this row and pivot, 637 // so that av becomes parametric 638 if (e.isConstant()) 639 { 640 // if there isn't another variable in the row 641 // then the tableau contains the equation av=0 -- 642 // just delete av's row 643 removeRow(av); 644 removeRow(az); 645 return; 646 } 647 ClAbstractVariable entryVar = e.anyPivotableVariable(); 648 pivot( entryVar, av); 649 } 650 assert(rowExpression(av) is null, "rowExpression(av) is null"); 651 removeColumn(av); 652 removeRow(az); 653 } 654 655 // We are trying to add the constraint expr=0 to the appropriate 656 // tableau. Try to add expr directly to the tableax without 657 // creating an artificial variable. Return true if successful and 658 // false if not. 659 protected final bool tryAddingDirectly(ClLinearExpression expr) 660 { 661 fnenterprint("tryAddingDirectly: " ~ expr.toString() ); 662 ClAbstractVariable subject = chooseSubject(expr); 663 if (subject is null ) 664 { 665 fnexitprint("returning false"); 666 return false; 667 } 668 expr.newSubject( subject); 669 if (columnsHasKey( subject)) 670 { 671 substituteOut( subject, expr); 672 } 673 addRow( subject, expr); 674 fnexitprint("returning true"); 675 return true; // successfully added directly 676 } 677 678 // We are trying to add the constraint expr=0 to the tableaux. Try 679 // to choose a subject (a variable to become basic) from among the 680 // current variables in expr. If expr contains any unrestricted 681 // variables, then we must choose an unrestricted variable as the 682 // subject. Also, if the subject is new to the solver we won't have 683 // to do any substitutions, so we prefer new variables to ones that 684 // are currently noted as parametric. If expr contains only 685 // restricted variables, if there is a restricted variable with a 686 // negative coefficient that is new to the solver we can make that 687 // the subject. Otherwise we can't find a subject, so return nil. 688 // (In this last case we have to add an artificial variable and use 689 // that variable as the subject -- this is done outside this method 690 // though.) 691 // 692 // Note: in checking for variables that are new to the solver, we 693 // ignore whether a variable occurs in the objective function, since 694 // new slack variables are added to the objective function by 695 // 'newExpression:', which is called before this method. 696 protected final ClAbstractVariable chooseSubject(ClLinearExpression expr) 697 { 698 fnenterprint("chooseSubject: " ~ expr.toString()); 699 ClAbstractVariable subject = null; // the current best subject, if any 700 701 bool foundUnrestricted = false; 702 bool foundNewRestricted = false; 703 704 auto terms = expr.terms(); 705 706 foreach(ClAbstractVariable v, double c; terms) 707 { 708 if (foundUnrestricted) 709 { 710 if (!v.isRestricted()) 711 { 712 if (!columnsHasKey(v)) 713 return v; 714 } 715 } 716 else 717 { 718 // we haven't found an restricted variable yet 719 if (v.isRestricted()) 720 { 721 if (!foundNewRestricted && !v.isDummy() && c < 0.0) 722 { 723 auto col = _columns.get(v, null); 724 if (col is null || 725 (col.length == 1 && columnsHasKey(_objective))) 726 { 727 subject = v; 728 foundNewRestricted = true; 729 } 730 } 731 } 732 else 733 { 734 subject = v; 735 foundUnrestricted = true; 736 } 737 } 738 } 739 740 if (subject !is null) 741 return subject; 742 743 double coeff = 0.0; 744 745 foreach(ClAbstractVariable v, double c; terms) 746 { 747 if (!v.isDummy()) 748 return null; // nope, no luck 749 if (!columnsHasKey(v)) 750 { 751 subject = v; 752 coeff = c; 753 } 754 } 755 756 if (!approxEqual(expr.constant(), 0.0)) 757 { 758 throw new ClErrorRequiredFailure(); 759 } 760 if (coeff > 0.0) 761 { 762 expr.multiplyMe(-1); 763 } 764 return subject; 765 } 766 767 // Each of the non-required edits will be represented by an equation 768 // of the form 769 // v = c + eplus - eminus 770 // where v is the variable with the edit, c is the previous edit 771 // value, and eplus and eminus are slack variables that hold the 772 // error in satisfying the edit constraint. We are about to change 773 // something, and we want to fix the constants in the equations 774 // representing the edit constraints. If one of eplus and eminus is 775 // basic, the other must occur only in the expression for that basic 776 // error variable. (They can't both be basic.) Fix the constant in 777 // this expression. Otherwise they are both nonbasic. Find all of 778 // the expressions in which they occur, and fix the constants in 779 // those. See the UIST paper for details. 780 // (This comment was for resetEditConstants(), but that is now 781 // gone since it was part of the screwey vector-based interface 782 // to resolveing. --02/16/99 gjb) 783 protected final void deltaEditConstant(double delta, 784 ClAbstractVariable plusErrorVar, 785 ClAbstractVariable minusErrorVar) 786 { 787 fnenterprint("deltaEditConstant :" ~ delta.to!string() ~ ", " ~ plusErrorVar.toString() ~ ", " ~ minusErrorVar.toString()); 788 ClLinearExpression exprPlus = rowExpression(plusErrorVar); 789 if (exprPlus !is null ) 790 { 791 exprPlus.incrementConstant(delta); 792 793 if (exprPlus.constant() < 0.0) 794 { 795 _infeasibleRows ~= plusErrorVar; 796 } 797 return; 798 } 799 800 ClLinearExpression exprMinus = rowExpression(minusErrorVar); 801 if (exprMinus !is null) 802 { 803 exprMinus.incrementConstant(-delta); 804 if (exprMinus.constant() < 0.0) 805 { 806 _infeasibleRows ~= minusErrorVar; 807 } 808 return; 809 } 810 811 auto columnVars = _columns[minusErrorVar]; 812 813 foreach(ClAbstractVariable basicVar; columnVars) 814 { 815 ClLinearExpression expr = rowExpression(basicVar); 816 //assert(expr != null, "expr != null" ); 817 double c = expr.coefficientFor(minusErrorVar); 818 expr.incrementConstant(c * delta); 819 if (basicVar.isRestricted() && expr.constant() < 0.0) 820 { 821 _infeasibleRows ~= basicVar; 822 } 823 } 824 } 825 826 // We have set new values for the constants in the edit constraints. 827 // Re-optimize using the dual simplex algorithm. 828 protected final void dualOptimize() 829 { 830 fnenterprint("dualOptimize:"); 831 ClLinearExpression zRow = rowExpression(_objective); 832 while (!_infeasibleRows.isEmpty()) 833 { 834 ClAbstractVariable exitVar = _infeasibleRows.anyElement; 835 _infeasibleRows.remove(exitVar); 836 ClAbstractVariable entryVar = null; 837 ClLinearExpression expr = rowExpression(exitVar); 838 if (expr !is null ) 839 { 840 if (expr.constant() < 0.0) 841 { 842 double ratio = double.max; 843 foreach(ClAbstractVariable v, double c; expr.terms()) 844 { 845 if (c > 0.0 && v.isPivotable()) 846 { 847 double zc = zRow.coefficientFor(v); 848 double r = zc/c; // FIXGJB r:= zc/c or zero, as ClSymbolicWeight-s 849 if (r < ratio) 850 { 851 entryVar = v; 852 ratio = r; 853 } 854 } 855 } 856 if (ratio == double.max) 857 { 858 throw new ClErrorInternal("ratio == nil (MAX_VALUE) in dualOptimize"); 859 } 860 pivot( entryVar, exitVar); 861 } 862 } 863 } 864 } 865 866 // Make a new linear expression representing the constraint cn, 867 // replacing any basic variables with their defining expressions. 868 // Normalize if necessary so that the constant is non-negative. If 869 // the constraint is non-required give its error variables an 870 // appropriate weight in the objective function. 871 protected final ClLinearExpression newExpression(ClConstraint cn, 872 ref ClAbstractVariable[] eplus_eminus, 873 ref double prevEConstant) 874 { 875 fnenterprint("newExpression: " ~ cn.toString()); 876 traceprint("cn.isInequality() == " ~ cn.isInequality().to!string()); 877 traceprint("cn.isRequired() == " ~ cn.isRequired().to!string()); 878 879 ClLinearExpression cnExpr = cn.expression(); 880 ClLinearExpression expr = new ClLinearExpression(cnExpr.constant()); 881 ClSlackVariable slackVar = new ClSlackVariable(); 882 ClDummyVariable dummyVar = new ClDummyVariable(); 883 ClSlackVariable eminus = new ClSlackVariable(); 884 ClSlackVariable eplus = new ClSlackVariable(); 885 foreach(ClAbstractVariable v, double c; cnExpr.terms()) 886 { 887 ClLinearExpression e = rowExpression(v); 888 if (e is null) 889 expr.addVariable(v, c); 890 else 891 expr.addExpression(e, c); 892 } 893 894 import std.stdio; 895 896 if (cn.isInequality()) 897 { 898 ++_slackCounter; 899 slackVar = new ClSlackVariable(_slackCounter, "s"); 900 expr.setVariable(slackVar, -1); 901 _markerVars[cn] = slackVar; 902 if (!cn.isRequired()) 903 { 904 ++_slackCounter; 905 eminus = new ClSlackVariable(_slackCounter, "em"); 906 expr.setVariable(eminus, 1.0); 907 ClLinearExpression zRow = rowExpression(_objective); 908 ClSymbolicWeight sw = cn.strength().symbolicWeight.times(cn.weight()); 909 zRow.setVariable( eminus, sw.asDouble()); 910 insertErrorVar(cn, eminus); 911 noteAddedVariable(eminus, _objective); 912 } 913 } 914 else 915 { 916 // cn is an equality 917 if (cn.isRequired()) 918 { 919 ++_dummyCounter; 920 dummyVar = new ClDummyVariable(_dummyCounter, "d"); 921 expr.setVariable(dummyVar, 1.0); 922 _markerVars[cn] = dummyVar; 923 traceprint("Adding dummyVar == d" ~ _dummyCounter.to!string()); 924 } 925 else 926 { 927 ++_slackCounter; 928 929 eplus = new ClSlackVariable(_slackCounter, "ep"); 930 eminus = new ClSlackVariable(_slackCounter, "em"); 931 932 expr.setVariable(eplus, -1.0); 933 expr.setVariable(eminus, 1.0); 934 935 _markerVars[cn] = eplus; 936 ClLinearExpression zRow = rowExpression(_objective); 937 ClSymbolicWeight sw = cn.strength().symbolicWeight.times(cn.weight()); 938 double swCoeff = sw.asDouble(); 939 940 if (swCoeff == 0) 941 { 942 traceprint("sw == " ~ sw.to!string()); 943 traceprint("cn == " ~ cn.to!string()); 944 traceprint("adding " ~ eplus.to!string() ~ " and " ~ eminus.to!string() ~ " with swCoeff == " ~ swCoeff.to!string()); 945 } 946 947 zRow.setVariable(eplus, swCoeff); 948 noteAddedVariable(eplus, _objective); 949 zRow.setVariable(eminus, swCoeff); 950 951 noteAddedVariable(eminus, _objective); 952 953 insertErrorVar(cn, eminus); 954 955 insertErrorVar(cn, eplus); 956 957 if (cn.isStayConstraint()) 958 { 959 _stayPlusErrorVars ~= eplus; 960 _stayMinusErrorVars ~= eminus; 961 } 962 else if (cn.isEditConstraint()) 963 { 964 eplus_eminus ~= eplus; 965 eplus_eminus ~= eminus; 966 prevEConstant = cnExpr.constant(); 967 } 968 } 969 } 970 971 if (expr.constant() < 0) 972 expr.multiplyMe(-1); 973 974 fnexitprint("returning " ~ expr.to!string()); 975 return expr; 976 } 977 978 // Minimize the value of the objective. (The tableau should already 979 // be feasible.) 980 protected final void optimize(ClObjectiveVariable zVar) 981 { 982 fnenterprint("optimize: " ~ zVar.toString()); 983 traceprint(this.toString()); 984 985 ClLinearExpression zRow = rowExpression(zVar); 986 assert(zRow !is null, "zRow != null"); 987 ClAbstractVariable entryVar = null; 988 ClAbstractVariable exitVar = null; 989 while (true) 990 { 991 double objectiveCoeff = 0; 992 foreach(ClAbstractVariable v, double c; zRow.terms()) 993 { 994 if (v.isPivotable() && c < objectiveCoeff) 995 { 996 objectiveCoeff = c; 997 entryVar = v; 998 } 999 } 1000 if (objectiveCoeff >= -_epsilon || entryVar is null) 1001 return; 1002 traceprint("entryVar == " ~ entryVar.to!string() ~ ", objectiveCoeff == " ~ objectiveCoeff.to!string()); 1003 1004 double minRatio = double.max; 1005 double r = 0.0; 1006 foreach(ClAbstractVariable v; _columns[entryVar]) 1007 { 1008 traceprint("Checking " ~ v.toString()); 1009 if (v.isPivotable()) 1010 { 1011 ClLinearExpression expr = rowExpression(v); 1012 double coeff = expr.coefficientFor(entryVar); 1013 traceprint("pivotable, coeff = " ~ coeff.to!string()); 1014 if (coeff < 0.0) 1015 { 1016 r = -expr.constant() / coeff; 1017 if (r < minRatio) 1018 { 1019 traceprint("New minratio == " ~ r.to!string()); 1020 minRatio = r; 1021 exitVar = v; 1022 } 1023 } 1024 } 1025 } 1026 if (minRatio == double.max) 1027 { 1028 throw new ClErrorInternal("Objective function is unbounded in optimize"); 1029 } 1030 pivot(entryVar, exitVar); 1031 traceprint(this.toString()); 1032 } 1033 } 1034 1035 // Do a pivot. Move entryVar into the basis (i.e. make it a basic variable), 1036 // and move exitVar out of the basis (i.e., make it a parametric variable) 1037 protected final void pivot(ClAbstractVariable entryVar, 1038 ClAbstractVariable exitVar) 1039 { 1040 fnenterprint("pivot: " ~ entryVar.toString() ~ ", " ~ exitVar.toString()); 1041 1042 // the entryVar might be non-pivotable if we're doing a removeConstraint -- 1043 // otherwise it should be a pivotable variable -- enforced at call sites, 1044 // hopefully 1045 1046 ClLinearExpression pexpr = removeRow(exitVar); 1047 1048 pexpr.changeSubject(exitVar, entryVar); 1049 substituteOut(entryVar, pexpr); 1050 addRow(entryVar, pexpr); 1051 } 1052 1053 // Each of the non-required stays will be represented by an equation 1054 // of the form 1055 // v = c + eplus - eminus 1056 // where v is the variable with the stay, c is the previous value of 1057 // v, and eplus and eminus are slack variables that hold the error 1058 // in satisfying the stay constraint. We are about to change 1059 // something, and we want to fix the constants in the equations 1060 // representing the stays. If both eplus and eminus are nonbasic 1061 // they have value 0 in the current solution, meaning the previous 1062 // stay was exactly satisfied. In this case nothing needs to be 1063 // changed. Otherwise one of them is basic, and the other must 1064 // occur only in the expression for that basic error variable. 1065 // Reset the constant in this expression to 0. 1066 protected final void resetStayConstants() 1067 { 1068 fnenterprint("resetStayConstants"); 1069 1070 for (int i = 0; i < _stayPlusErrorVars.length; i++) 1071 { 1072 ClLinearExpression expr = rowExpression(_stayPlusErrorVars[i]); 1073 if (expr is null ) 1074 expr = rowExpression(_stayMinusErrorVars[i]); 1075 if (expr !is null) 1076 expr.set_constant(0.0); 1077 } 1078 } 1079 1080 // Set the external variables known to this solver to their appropriate values. 1081 // Set each external basic variable to its value, and set each 1082 // external parametric variable to 0. (It isn't clear that we will 1083 // ever have external parametric variables -- every external 1084 // variable should either have a stay on it, or have an equation 1085 // that defines it in terms of other external variables that do have 1086 // stays. For the moment I'll put this in though.) Variables that 1087 // are internal to the solver don't actually store values -- their 1088 // values are just implicit in the tableu -- so we don't need to set 1089 // them. 1090 protected final void setExternalVariables() 1091 { 1092 fnenterprint("setExternalVariables:"); 1093 traceprint(this.toString()); 1094 1095 foreach(ClAbstractVariable v; _externalParametricVars) 1096 { 1097 if (rowExpression(v) !is null) 1098 { 1099 writeln("Error: variable" ~ v.toString() ~ 1100 " in _externalParametricVars is basic"); 1101 continue; 1102 } 1103 (cast(ClVariable)v).change_value(0.0); 1104 } 1105 1106 foreach(ClAbstractVariable v; _externalRows) 1107 { 1108 ClLinearExpression expr = rowExpression(v); 1109 debugprint("v == " ~ v.toString()); 1110 debugprint("expr == " ~ expr.toString()); 1111 (cast(ClVariable)v).change_value(expr.constant()); 1112 } 1113 1114 _fNeedsSolving = false; 1115 } 1116 1117 // Protected convenience function to insert an error variable into 1118 // the _errorVars set, creating the mapping with put as necessary 1119 protected final void insertErrorVar(ClConstraint cn, ClAbstractVariable var) 1120 { 1121 fnenterprint("insertErrorVar:" ~ cn.toString() ~ ", " ~ var.toString()); 1122 1123 auto cnset = _errorVars.get(cn, null); 1124 if (cnset is null) 1125 { 1126 cnset = new typeof(cnset)(); 1127 _errorVars[cn] = cnset; 1128 } 1129 cnset ~= var; 1130 } 1131 1132 // Control whether optimization and setting of external variables 1133 // is done automatically or not. By default it is done 1134 // automatically and solve() never needs to be explicitly 1135 // called by client code; if autosolve is put to false, 1136 // then solve() needs to be invoked explicitly before using 1137 // variables' values 1138 // (Turning off autosolve while adding lots and lots of 1139 // constraints [ala the addDel test in ClTests] saved 1140 // about 20% in runtime, from 68sec to 54sec for 900 constraints, 1141 // with 126 failed adds) 1142 bool autosolve = true; 1143 1144 //// BEGIN PRIVATE INSTANCE FIELDS 1145 1146 // the arrays of positive and negative error vars for the stay constraints 1147 // (need both positive and negative since they have only non-negative values) 1148 private ClAbstractVariable[] _stayMinusErrorVars; 1149 private ClAbstractVariable[] _stayPlusErrorVars; 1150 1151 // give error variables for a non required constraint, 1152 // maps to ClSlackVariable-s 1153 private Set!ClAbstractVariable[ClConstraint] _errorVars; // map ClConstraint to Set (of ClVariable) 1154 1155 1156 // Return a lookup table giving the marker variable for each 1157 // constraint (used when deleting a constraint). 1158 private ClAbstractVariable[ClConstraint] _markerVars; // map ClConstraint to ClVariable 1159 1160 private ClObjectiveVariable _objective; 1161 1162 // Map edit variables to ClEditInfo-s. 1163 // ClEditInfo instances contain all the information for an 1164 // edit constraint (the edit plus/minus vars, the index [for old-style 1165 // resolve(Vector...) interface], and the previous value. 1166 // (ClEditInfo replaces the parallel vectors from the Smalltalk impl.) 1167 private ClEditInfo[ClVariable] _editVarMap; // map ClVariable to a ClEditInfo 1168 1169 private long _slackCounter = 0; 1170 private long _artificialCounter = 0; 1171 private long _dummyCounter = 0; 1172 1173 private double[] _resolve_pair; 1174 1175 private enum double _epsilon = 1e-8; 1176 1177 private bool _fNeedsSolving = false; 1178 1179 private size_t[] _stkCedcns; 1180 }