/* Cassowary.net: an incremental constraint solver for .NET (http://lumumba.uhasselt.be/jo/projects/cassowary.net/) Copyright (C) 2005 Jo Vermeulen (jo.vermeulen@uhasselt.be) This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ using System; using System.Collections; using Cassowary.Utils; namespace Cassowary { public class ClSimplexSolver : ClTableau { /// /// Constructor initializes the fields, and creaties the objective row. /// public ClSimplexSolver() { _stayMinusErrorVars = new ArrayList(); _stayPlusErrorVars = new ArrayList(); _errorVars = new Hashtable(); _markerVars = new Hashtable(); _resolve_pair = new ArrayList(2); _resolve_pair.Add(new ClDouble(0)); _resolve_pair.Add(new ClDouble(0)); _objective = new ClObjectiveVariable("Z"); _editVarMap = new Hashtable(); _slackCounter = 0; _artificialCounter = 0; _dummyCounter = 0; _epsilon = 1e-8; _cOptimizeAutomatically = true; _cNeedsSolving = false; ClLinearExpression e = new ClLinearExpression(); _rows.Add(_objective, e); _stkCedcns = new Stack(); _stkCedcns.Push(0); if (Trace) TracePrint("objective expr == " + RowExpression(_objective)); } /// /// Convenience function for creating a linear inequality constraint. /// public ClSimplexSolver AddLowerBound(ClAbstractVariable v, double lower) /* throws ExClRequiredFailure, ExClInternalError */ { ClLinearInequality cn = new ClLinearInequality(v, Cl.GEQ, new ClLinearExpression(lower)); return AddConstraint(cn); } /// /// Convenience function for creating a linear inequality constraint. /// public ClSimplexSolver AddUpperBound(ClAbstractVariable v, double upper) /* throws ExClRequiredFailure, ExClInternalError */ { ClLinearInequality cn = new ClLinearInequality(v, Cl.LEQ, new ClLinearExpression(upper)); return AddConstraint(cn); } /// /// Convenience function for creating a pair of linear inequality constraints. /// public ClSimplexSolver AddBounds(ClAbstractVariable v, double lower, double upper) /* throws ExClRequiredFailure, ExClInternalError */ { AddLowerBound(v, lower); AddUpperBound(v, upper); return this; } /// /// Add a constraint to the solver. /// /// The constraint to be added. /// /// public ClSimplexSolver AddConstraint(ClConstraint cn) /* throws ExClRequiredFailure, ExClInternalError */ { if (Trace) FnEnterPrint("AddConstraint: " + cn); ArrayList eplus_eminus = new ArrayList(2); ClDouble prevEConstant = new ClDouble(); ClLinearExpression expr = NewExpression(cn, /* output to: */ eplus_eminus, prevEConstant); bool cAddedOkDirectly = false; try { cAddedOkDirectly = TryAddingDirectly(expr); if (!cAddedOkDirectly) { // could not add directly AddWithArtificialVariable(expr); } } catch (ExClRequiredFailure rf) { throw rf; } _cNeedsSolving = true; if (cn.IsEditConstraint) { int i = _editVarMap.Count; ClEditConstraint cnEdit = (ClEditConstraint) cn; ClSlackVariable clvEplus = (ClSlackVariable) eplus_eminus[0]; ClSlackVariable clvEminus = (ClSlackVariable) eplus_eminus[1]; _editVarMap.Add(cnEdit.Variable, new ClEditInfo(cnEdit, clvEplus, clvEminus, prevEConstant.Value, i)); } if (_cOptimizeAutomatically) { Optimize(_objective); SetExternalVariables(); } return this; } /// /// Same as AddConstraint, throws no exceptions. /// /// False if the constraint resulted in an unsolvable system, otherwise true. /// /// public bool AddConstraintNoException(ClConstraint cn) { if (Trace) FnEnterPrint("AddConstraintNoException: " + cn); try { AddConstraint(cn); return true; } catch (ExClRequiredFailure rf) { return false; } } /// /// Add an edit constraint for a variable with a given strength. /// Variable to add an edit constraint to. /// Strength of the edit constraint. /// public ClSimplexSolver AddEditVar(ClVariable v, ClStrength strength) /* throws ExClInternalError */ { try { ClEditConstraint cnEdit = new ClEditConstraint(v, strength); return AddConstraint(cnEdit); } catch (ExClRequiredFailure rf) { // should not get this throw new ExClInternalError("Required failure when adding an edit variable"); } } /// /// Add an edit constraint with strength ClStrength#Strong. /// public ClSimplexSolver AddEditVar(ClVariable v) { /* throws ExClInternalError */ return AddEditVar(v, ClStrength.Strong); } /// /// Remove the edit constraint previously added. /// Variable to which the edit constraint was added before. /// public ClSimplexSolver RemoveEditVar(ClVariable v) /* throws ExClInternalError, ExClConstraintNotFound */ { ClEditInfo cei = (ClEditInfo) _editVarMap[v]; ClConstraint cn = cei.Constraint; RemoveConstraint(cn); return this; } /// /// Marks the start of an edit session. /// /// /// BeginEdit should be called before sending Resolve() /// messages, after adding the appropriate edit variables. /// public ClSimplexSolver BeginEdit() /* throws ExClInternalError */ { Assert(_editVarMap.Count > 0, "_editVarMap.Count > 0"); // may later want to do more in here _infeasibleRows.Clear(); ResetStayConstants(); _stkCedcns.Push(_editVarMap.Count); return this; } /// /// Marks the end of an edit session. /// /// /// EndEdit should be called after editing has finished for now, it /// just removes all edit variables. /// public ClSimplexSolver EndEdit() /* throws ExClInternalError */ { Assert(_editVarMap.Count > 0, "_editVarMap.Count > 0"); Resolve(); _stkCedcns.Pop(); int n = (int) _stkCedcns.Peek(); RemoveEditVarsTo(n); // may later want to do more in hore return this; } /// /// Eliminates all the edit constraints that were added. /// public ClSimplexSolver RemoveAllEditVars(int n) /* throws ExClInternalError */ { return RemoveEditVarsTo(0); } /// /// Remove the last added edit vars to leave only /// a specific number left. /// /// Number of edit variables to keep. /// /// public ClSimplexSolver RemoveEditVarsTo(int n) /* throws ExClInternalError */ { // HACK: to be able to remove elements from _editVarMap, // which will be done by RemoveEditVar(...). // // C# enumerators do not allow this, because they // only take a snapshot of the collection. If the collection // changes (by removing an element for example), the // enumerator gets out of sync, and an // InvalidOperationException will be thrown. // // We thus create a copy of the _editVarMap to iterate // through. Hashtable editVarMapCopy = (Hashtable) _editVarMap.Clone(); try { foreach (ClVariable v in editVarMapCopy.Keys) { ClEditInfo cei = (ClEditInfo) editVarMapCopy[v]; if (cei.Index >= n) { RemoveEditVar(v); } } Assert(_editVarMap.Count == n, "_editVarMap.Count == n"); return this; } catch (ExClConstraintNotFound cnf) { // should not get this throw new ExClInternalError("Constraint not found in RemoveEditVarsTo"); } } /// /// Add weak stays to the x and y parts of each point. These /// have increasing weights so that the solver will try to satisfy /// the x and y stays on the same point, rather than the x stay on /// one and the y stay on another. /// /// List of points to add weak stay constraints for. /// /// public ClSimplexSolver AddPointStays(ArrayList listOfPoints) /* throws ExClRequiredFailure, ExClInternalError */ { if (Trace) FnEnterPrint("AddPointStays: " + listOfPoints); double weight = 1.0; const double multiplier = 2.0; foreach (ClPoint p in listOfPoints) { AddPointStay(p, weight); weight *= multiplier; } return this; } public ClSimplexSolver AddPointStay(ClVariable vx, ClVariable vy, double weight) /* throws ExClRequiredFailure, ExClInternalError */ { AddStay(vx, ClStrength.Weak, weight); AddStay(vy, ClStrength.Weak, weight); return this; } public ClSimplexSolver AddPointStay(ClVariable vx, ClVariable vy) /* throws ExClRequiredFailure, ExClInternalError */ { AddPointStay(vx, vy, 1.0); return this; } public ClSimplexSolver AddPointStay(ClPoint clp, double weight) /* throws ExClRequiredFailure, ExClInternalError */ { AddStay(clp.X, ClStrength.Weak, weight); AddStay(clp.Y, ClStrength.Weak, weight); return this; } public ClSimplexSolver AddPointStay(ClPoint clp) /* throws ExClRequiredFailure, ExClInternalError */ { AddPointStay(clp, 1.0); return this; } /// /// Add a stay of the given strength (default to ClStrength#Weak) /// of a variable to the tableau.. /// /// Variable to add the stay constraint to. /// /// public ClSimplexSolver AddStay(ClVariable v, ClStrength strength, double weight) /* throws ExClRequiredFailure, ExClInternalError */ { ClStayConstraint cn = new ClStayConstraint(v, strength, weight); return AddConstraint(cn); } /// /// Default to weight 1.0. /// public ClSimplexSolver AddStay(ClVariable v, ClStrength strength) /* throws ExClRequiredFailure, ExClInternalError */ { AddStay(v, strength, 1.0); return this; } /// /// Default to strength ClStrength#Weak. /// public ClSimplexSolver AddStay(ClVariable v) /* throws ExClRequiredFailure, ExClInternalError */ { AddStay(v, ClStrength.Weak, 1.0); return this; } /// /// Remove a constraint from the tableau. /// Also remove any error variable associated with it. /// public ClSimplexSolver RemoveConstraint(ClConstraint cn) /* throws ExClRequiredFailure, ExClInternalError */ { if (Trace) { FnEnterPrint("RemoveConstraint: " + cn); TracePrint(this.ToString()); } _cNeedsSolving = true; ResetStayConstants(); ClLinearExpression zRow = RowExpression(_objective); Set eVars = (Set) _errorVars[cn]; if (Trace) TracePrint("eVars == " + eVars); if (eVars != null) { foreach (ClAbstractVariable clv in eVars) { ClLinearExpression expr = RowExpression(clv); if (expr == null) { zRow.AddVariable(clv, -cn.Weight * cn.Strength.SymbolicWeight.AsDouble(), _objective, this); } else // the error variable was in the basis { zRow.AddExpression(expr, -cn.Weight * cn.Strength.SymbolicWeight.AsDouble(), _objective, this); } } } int markerVarsCount = _markerVars.Count; ClAbstractVariable marker = (ClAbstractVariable) _markerVars[cn]; _markerVars.Remove(cn); if (markerVarsCount == _markerVars.Count) // key was not found { throw new ExClConstraintNotFound(); } if (Trace) TracePrint("Looking to remove var " + marker); if (RowExpression(marker) == null) { // not in the basis, so need to do some more work Set col = (Set) _columns[marker]; if (Trace) TracePrint("Must pivot -- columns are " + col); ClAbstractVariable exitVar = null; double minRatio = 0.0; foreach (ClAbstractVariable v in col) { if (v.IsRestricted) { ClLinearExpression expr = RowExpression(v); double coeff = expr.CoefficientFor(marker); if (Trace) TracePrint("Marker " + marker + "'s coefficient in " + expr + " is " + coeff); if (coeff < 0.0) { double r = -expr.Constant / coeff; if (exitVar == null || r < minRatio) { minRatio = r; exitVar = v; } } } } if (exitVar == null) { if (Trace) TracePrint("exitVar is still null"); foreach (ClAbstractVariable v in col) { if (v.IsRestricted) { ClLinearExpression expr = RowExpression(v); double coeff = expr.CoefficientFor(marker); double r = expr.Constant / coeff; if (exitVar == null || r < minRatio) { minRatio = r; exitVar = v; } } } } if (exitVar == null) { // exitVar is still null if (col.Count == 0) { RemoveColumn(marker); } else { // put first element in exitVar IEnumerator colEnum = col.GetEnumerator(); colEnum.MoveNext(); exitVar = (ClAbstractVariable) colEnum.Current; } } if (exitVar != null) { Pivot(marker, exitVar); } } if (RowExpression(marker) != null) { RemoveRow(marker); } if (eVars != null) { foreach (ClAbstractVariable v in eVars) { // FIXME: decide wether to use equals or != if (v != marker) { RemoveColumn(v); // v = null; // is read-only, cannot be set to null } } } if (cn.IsStayConstraint) { if (eVars != null) { for (int i = 0; i < _stayPlusErrorVars.Count; i++) { eVars.Remove(_stayPlusErrorVars[i]); eVars.Remove(_stayMinusErrorVars[i]); } } } else if (cn.IsEditConstraint) { Assert(eVars != null, "eVars != null"); ClEditConstraint cnEdit = (ClEditConstraint) cn; ClVariable clv = cnEdit.Variable; ClEditInfo cei = (ClEditInfo) _editVarMap[clv]; ClSlackVariable clvEditMinus = cei.ClvEditMinus; RemoveColumn(clvEditMinus); _editVarMap.Remove(clv); } // FIXME: do the remove at top if (eVars != null) { _errorVars.Remove(eVars); } marker = null; if (_cOptimizeAutomatically) { Optimize(_objective); SetExternalVariables(); } return this; } /// /// Re-initialize this solver from the original constraints, thus /// getting rid of any accumulated numerical problems /// /// /// Actually, we haven't definitely observed any such problems yet. /// public void Reset() /* throws ExClInternalError */ { if (Trace) FnEnterPrint("Reset"); throw new ExClInternalError("Reset not implemented"); } /// /// Re-solve the current collection of constraints for new values /// for the constants of the edit variables. /// /// /// Deprecated. Use SuggestValue(...) then Resolve(). If you must /// use this, be sure to not use it if you /// remove an edit variable (or edit constraints) from the middle /// of a list of edits, and then try to resolve with this function /// (you'll get the wrong answer, because the indices will be wrong /// in the ClEditInfo objects). /// public void Resolve(ArrayList newEditConstants) /* throws ExClInternalError */ { if (Trace) FnEnterPrint("Resolve " + newEditConstants); foreach (ClVariable v in _editVarMap.Keys) { ClEditInfo cei = (ClEditInfo) _editVarMap[v]; int i = cei.Index; try { if (i < newEditConstants.Count) { SuggestValue(v, ((ClDouble) newEditConstants[i]).Value); } } catch (ExClError e) { throw new ExClInternalError("Error during resolve"); } } Resolve(); } /// /// Convenience function for resolve-s of two variables. /// public void Resolve(double x, double y) /* throws ExClInternalError */ { ((ClDouble) _resolve_pair[0]).Value = x; ((ClDouble) _resolve_pair[1]).Value = y; Resolve(_resolve_pair); } /// /// Re-solve the current collection of constraints, given the new /// values for the edit variables that have already been /// suggested (see method). /// public void Resolve() /* throws ExClInternalError */ { if (Trace) FnEnterPrint("Resolve()"); DualOptimize(); SetExternalVariables(); _infeasibleRows.Clear(); ResetStayConstants(); } /// /// Suggest a new value for an edit variable. /// /// /// The variable needs to be added as an edit variable and /// BeginEdit() needs to be called before this is called. /// The tableau will not be solved completely until after Resolve() /// has been called. /// public ClSimplexSolver SuggestValue(ClVariable v, double x) /* throws ExClError */ { if (Trace) FnEnterPrint("SuggestValue(" + v + ", " + x + ")"); ClEditInfo cei = (ClEditInfo) _editVarMap[v]; if (cei == null) { #if !COMPACT Console.Error.WriteLine("SuggestValue for variable " + v + ", but var is not an edit variable\n"); #else Console.WriteLine("SuggestValue for variable " + v + ", but var is not an edit variable\n"); #endif throw new ExClError(); } int i = cei.Index; ClSlackVariable clvEditPlus = cei.ClvEditPlus; ClSlackVariable clvEditMinus = cei.ClvEditMinus; double delta = x - cei.PrevEditConstant; cei.PrevEditConstant = x; DeltaEditConstant(delta, clvEditPlus, clvEditMinus); return this; } /// /// Controls wether optimization and setting of external variables is done /// automatically or not. /// /// /// By default it is done automatically and never needs /// to be explicitly called by client code. If is /// put to false, then needs to be invoked explicitly /// before using variables' values. /// (Turning off while addings lots and lots /// of constraints [ala the AddDel test in ClTests] saved about 20 % in /// runtime, from 60sec to 54sec for 900 constraints, with 126 failed adds). /// public bool AutoSolve { get { return _cOptimizeAutomatically; } set { _cOptimizeAutomatically = value; } } public ClSimplexSolver Solve() /* throws ExClInternalError */ { if (_cNeedsSolving) { Optimize(_objective); SetExternalVariables(); } return this; } public ClSimplexSolver SetEditedValue(ClVariable v, double n) /* throws ExClInternalError */ { if (!ContainsVariable(v)) { v.ChangeValue(n); return this; } if (!Cl.Approx(n, v.Value)) { AddEditVar(v); BeginEdit(); try { SuggestValue(v, n); } catch(ExClError e) { // just added it above, so we shouldn't get an error throw new ExClInternalError("Error in SetEditedValue"); } EndEdit(); } return this; } public bool ContainsVariable(ClVariable v) /* throws ExClInternalError */ { return ColumnsHasKey(v) || (RowExpression(v) != null); } public ClSimplexSolver AddVar(ClVariable v) /* throws ExClInternalError */ { if (!ContainsVariable(v)) { try { AddStay(v); } catch(ExClRequiredFailure rf) { // cannot have a required failure, since we add w/ weak throw new ExClInternalError("Error in AddVar -- required failure is impossible"); } if (Trace) TracePrint("added initial stay on " + v); } return this; } /// /// Returns information about the solver's internals. /// /// /// Originally from Michael Noth /// /// /// String containing the information. /// public override string GetInternalInfo() { string result = base.GetInternalInfo(); result += "\nSolver info:\n"; result += "Stay Error Variables: "; result += _stayPlusErrorVars.Count + _stayMinusErrorVars.Count; result += " (" +_stayPlusErrorVars.Count + " +, "; result += _stayMinusErrorVars.Count + " -)\n"; result += "Edit Variables: " + _editVarMap.Count; result += "\n"; return result; } public string GetDebugInfo() { string result = ToString(); result += GetInternalInfo(); result += "\n"; return result; } public override string ToString() { string result = base.ToString(); result += "\n_stayPlusErrorVars: "; result += _stayPlusErrorVars; result += "\n_stayMinusErrorVars: "; result += _stayMinusErrorVars; result += "\n"; return result; } public Hashtable ConstraintMap { get { return _markerVars; } } //// END PUBLIC INTERFACE //// /// /// Add the constraint expr=0 to the inequality tableau using an /// artificial variable. /// /// /// To do this, create an artificial variable av and add av=expr /// to the inequality tableau, then make av be 0 (raise an exception /// if we can't attain av=0). /// protected void AddWithArtificialVariable(ClLinearExpression expr) /* throws ExClRequiredFailure, ExClInternalError */ { if (Trace) FnEnterPrint("AddWithArtificialVariable: " + expr); ClSlackVariable av = new ClSlackVariable(++_artificialCounter, "a"); ClObjectiveVariable az = new ClObjectiveVariable("az"); ClLinearExpression azRow = (ClLinearExpression) expr.Clone(); if (Trace) TracePrint("before AddRows:\n" + this); AddRow(az, azRow); AddRow(av, expr); if (Trace) TracePrint("after AddRows:\n" + this); Optimize(az); ClLinearExpression azTableauRow = RowExpression(az); if (Trace) TracePrint("azTableauRow.Constant == " + azTableauRow.Constant); if (!Cl.Approx(azTableauRow.Constant, 0.0)) { RemoveRow(az); RemoveColumn(av); throw new ExClRequiredFailure(); } // see if av is a basic variable ClLinearExpression e = RowExpression(av); if (e != null) { // find another variable in this row and pivot, // so that av becomes parametric if (e.IsConstant) { // if there isn't another variable in the row // then the tableau contains the equation av=0 -- // just delete av's row RemoveRow(av); RemoveRow(az); return; } ClAbstractVariable entryVar = e.AnyPivotableVariable(); Pivot(entryVar, av); } Assert(RowExpression(av) == null, "RowExpression(av) == null)"); RemoveColumn(av); RemoveRow(az); } /// /// Try to add expr directly to the tableau without creating an /// artificial variable. /// /// /// We are trying to add the constraint expr=0 to the appropriate /// tableau. /// /// /// True if successful and false if not. /// protected bool TryAddingDirectly(ClLinearExpression expr) /* throws ExClRequiredFailure */ { if (Trace) FnEnterPrint("TryAddingDirectly: " + expr); ClAbstractVariable subject = ChooseSubject(expr); if (subject == null) { if (Trace) FnExitPrint("returning false"); return false; } expr.NewSubject(subject); if (ColumnsHasKey(subject)) { SubstituteOut(subject, expr); } AddRow(subject, expr); if (Trace) FnExitPrint("returning true"); return true; // succesfully added directly } /// /// Try to choose a subject (a variable to become basic) from /// among the current variables in expr. /// /// /// We are trying to add the constraint expr=0 to the tableaux. /// If expr constains any unrestricted variables, then we must choose /// an unrestricted variable as the subject. Also if the subject is /// new to the solver, we won't have to do any substitutions, so we /// prefer new variables to ones that are currently noted as parametric. /// If expr contains only restricted variables, if there is a restricted /// variable with a negative coefficient that is new to the solver we can /// make that the subject. Otherwise we can't find a subject, so return nil. /// (In this last case we have to add an artificial variable and use that /// variable as the subject -- this is done outside this method though.) /// protected ClAbstractVariable ChooseSubject(ClLinearExpression expr) /* ExClRequiredFailure */ { if (Trace) FnEnterPrint("ChooseSubject: " + expr); ClAbstractVariable subject = null; // the current best subject, if any bool foundUnrestricted = false; bool foundNewRestricted = false; Hashtable terms = expr.Terms; foreach (ClAbstractVariable v in terms.Keys) { double c = ((ClDouble) terms[v]).Value; if (foundUnrestricted) { if (!v.IsRestricted) { if (!ColumnsHasKey(v)) return v; } } else { // we haven't found an restricted variable yet if (v.IsRestricted) { if (!foundNewRestricted && !v.IsDummy && c < 0.0) { Set col = (Set) _columns[v]; if ( col == null || (col.Count == 1 && ColumnsHasKey(_objective)) ) { subject = v; foundNewRestricted = true; } } } else { subject = v; foundUnrestricted = true; } } } if (subject != null) return subject; double coeff = 0.0; foreach (ClAbstractVariable v in terms.Keys) { double c = ((ClDouble) terms[v]).Value; if (!v.IsDummy) return null; // nope, no luck if (!ColumnsHasKey(v)) { subject = v; coeff = c; } } if (!Cl.Approx(expr.Constant, 0.0)) { throw new ExClRequiredFailure(); } if (coeff > 0.0) { expr.MultiplyMe(-1); } return subject; } protected ClLinearExpression NewExpression(ClConstraint cn, ArrayList eplus_eminus, ClDouble prevEConstant) { if (Trace) { FnEnterPrint("NewExpression: " + cn); TracePrint("cn.IsInequality == " + cn.IsInequality); TracePrint("cn.IsRequired == " + cn.IsRequired); } ClLinearExpression cnExpr = cn.Expression; ClLinearExpression expr = new ClLinearExpression(cnExpr.Constant); ClSlackVariable slackVar = new ClSlackVariable(); ClDummyVariable dummyVar = new ClDummyVariable(); ClSlackVariable eminus = new ClSlackVariable(); ClSlackVariable eplus = new ClSlackVariable(); Hashtable cnTerms = cnExpr.Terms; foreach(ClAbstractVariable v in cnTerms.Keys) { double c = ((ClDouble) cnTerms[v]).Value; ClLinearExpression e = RowExpression(v); if (e == null) expr.AddVariable(v, c); else expr.AddExpression(e, c); } if (cn.IsInequality) { ++_slackCounter; slackVar = new ClSlackVariable (_slackCounter, "s"); expr.SetVariable(slackVar, -1); _markerVars.Add(cn, slackVar); if (!cn.IsRequired) { ++_slackCounter; eminus = new ClSlackVariable(_slackCounter, "em"); expr.SetVariable(eminus, 1.0); ClLinearExpression zRow = RowExpression(_objective); ClSymbolicWeight sw = cn.Strength.SymbolicWeight.Times(cn.Weight); zRow.SetVariable(eminus, sw.AsDouble()); InsertErrorVar(cn, eminus); NoteAddedVariable(eminus, _objective); } } else { // cn is an equality if (cn.IsRequired) { ++_dummyCounter; dummyVar = new ClDummyVariable(_dummyCounter, "d"); expr.SetVariable(dummyVar, 1.0); _markerVars.Add(cn, dummyVar); if (Trace) TracePrint("Adding dummyVar == d" + _dummyCounter); } else { ++_slackCounter; eplus = new ClSlackVariable(_slackCounter, "ep"); eminus = new ClSlackVariable(_slackCounter, "em"); expr.SetVariable(eplus, -1.0); expr.SetVariable(eminus, 1.0); _markerVars.Add(cn, eplus); ClLinearExpression zRow = RowExpression(_objective); ClSymbolicWeight sw = cn.Strength.SymbolicWeight.Times(cn.Weight); double swCoeff = sw.AsDouble(); if (swCoeff == 0) { if (Trace) { TracePrint("sw == " + sw); TracePrint("cn == " + cn); TracePrint("adding " + eplus + " and " + eminus + " with swCoeff == " + swCoeff); } } zRow.SetVariable(eplus, swCoeff); NoteAddedVariable(eplus, _objective); zRow.SetVariable(eminus, swCoeff); NoteAddedVariable(eminus, _objective); InsertErrorVar(cn, eminus); InsertErrorVar(cn, eplus); if (cn.IsStayConstraint) { _stayPlusErrorVars.Add(eplus); _stayMinusErrorVars.Add(eminus); } else if (cn.IsEditConstraint) { eplus_eminus.Add(eplus); eplus_eminus.Add(eminus); prevEConstant.Value = cnExpr.Constant; } } } if (expr.Constant < 0) expr.MultiplyMe(-1); if (Trace) FnExitPrint("returning " + expr); return expr; } /// /// Minimize the value of the objective. /// /// /// The tableau should already be feasible. /// protected void Optimize(ClObjectiveVariable zVar) /* throws ExClInternalError */ { if (Trace) { FnEnterPrint("Optimize: " + zVar); TracePrint(this.ToString()); } ClLinearExpression zRow = RowExpression(zVar); Assert(zRow != null, "zRow != null"); ClAbstractVariable entryVar = null; ClAbstractVariable exitVar = null; while (true) { double objectiveCoeff = 0; Hashtable terms = zRow.Terms; foreach (ClAbstractVariable v in terms.Keys) { double c = ((ClDouble) terms[v]).Value; if (v.IsPivotable && c < objectiveCoeff) { objectiveCoeff = c; entryVar = v; } } if (objectiveCoeff >= -_epsilon || entryVar == null) return; if (Trace) TracePrint("entryVar == " + entryVar + ", objectiveCoeff == " + objectiveCoeff); double minRatio = Double.MaxValue; Set columnVars = (Set) _columns[entryVar]; double r = 0.0; foreach (ClAbstractVariable v in columnVars) { if (Trace) TracePrint("Checking " + v); if (v.IsPivotable) { ClLinearExpression expr = RowExpression(v); double coeff = expr.CoefficientFor(entryVar); if (Trace) TracePrint("pivotable, coeff == " + coeff); if (coeff < 0.0) { r = - expr.Constant / coeff; if (r < minRatio) { if (Trace) TracePrint("New minRatio == " + r); minRatio = r; exitVar = v; } } } } if (minRatio == Double.MaxValue) { throw new ExClInternalError("Objective function is unbounded in Optimize"); } Pivot(entryVar, exitVar); if (Trace) TracePrint(this.ToString()); } } /// /// Fix the constants in the equations representing the edit constraints. /// /// /// Each of the non-required edits will be represented by an equation /// of the form: /// v = c + eplus - eminus /// where v is the variable with the edit, c is the previous edit value, /// and eplus and eminus are slack variables that hold the error in /// satisfying the edit constraint. We are about to change something, /// and we want to fix the constants in the equations representing /// the edit constraints. If one of eplus and eminus is basic, the other /// must occur only in the expression for that basic error variable. /// (They can't both be basic.) Fix the constant in this expression. /// Otherwise they are both non-basic. Find all of the expressions /// in which they occur, and fix the constants in those. See the /// UIST paper for details. /// (This comment was for ResetEditConstants(), but that is now /// gone since it was part of the screwey vector-based interface /// to resolveing. --02/16/99 gjb) /// protected void DeltaEditConstant(double delta, ClAbstractVariable plusErrorVar, ClAbstractVariable minusErrorVar) { if (Trace) FnEnterPrint("DeltaEditConstant :" + delta + ", " + plusErrorVar + ", " + minusErrorVar); ClLinearExpression exprPlus = RowExpression(plusErrorVar); if (exprPlus != null) { exprPlus.IncrementConstant(delta); if (exprPlus.Constant < 0.0) { _infeasibleRows.Add(plusErrorVar); } return; } ClLinearExpression exprMinus = RowExpression(minusErrorVar); if (exprMinus != null) { exprMinus.IncrementConstant(-delta); if (exprMinus.Constant < 0.0) { _infeasibleRows.Add(minusErrorVar); } return; } Set columnVars = (Set) _columns[minusErrorVar]; foreach (ClAbstractVariable basicVar in columnVars) { ClLinearExpression expr = RowExpression(basicVar); //Assert(expr != null, "expr != null"); double c = expr.CoefficientFor(minusErrorVar); expr.IncrementConstant(c * delta); if (basicVar.IsRestricted && expr.Constant < 0.0) { _infeasibleRows.Add(basicVar); } } } /// /// Re-optimize using the dual simplex algorithm. /// /// /// We have set new values for the constants in the edit constraints. /// protected void DualOptimize() /* throws ExClInternalError */ { if (Trace) FnEnterPrint("DualOptimize: "); ClLinearExpression zRow = RowExpression(_objective); while (!_infeasibleRows.Empty) { IEnumerator enumIfRows = _infeasibleRows.GetEnumerator(); enumIfRows.MoveNext(); ClAbstractVariable exitVar = (ClAbstractVariable) enumIfRows.Current; _infeasibleRows.Remove(exitVar); ClAbstractVariable entryVar = null; ClLinearExpression expr = RowExpression(exitVar); if (expr != null) { if (expr.Constant < 0.0) { double ratio = Double.MaxValue; double r; Hashtable terms = expr.Terms; foreach (ClAbstractVariable v in terms.Keys) { double c = ((ClDouble) terms[v]).Value; if (c > 0.0 && v.IsPivotable) { double zc = zRow.CoefficientFor(v); r = zc / c; // FIXME: zc / c or zero, as ClSymbolicWeigth-s if (r < ratio) { entryVar = v; ratio = r; } } } if (ratio == Double.MaxValue) { throw new ExClInternalError("ratio == nil (Double.MaxValue) in DualOptimize"); } Pivot(entryVar, exitVar); } } } } /// /// Do a pivot. Move entryVar into the basis and move exitVar /// out of the basis. /// /// /// We could for example make entryVar a basic variable and /// make exitVar a parametric variable. /// protected void Pivot(ClAbstractVariable entryVar, ClAbstractVariable exitVar) /* throws ExClInternalError */ { if (Trace) FnEnterPrint("Pivot: " + entryVar + ", " + exitVar); // the entryVar might be non-pivotable if we're doing a // RemoveConstraint -- otherwise it should be a pivotable // variable -- enforced at call sites, hopefully ClLinearExpression pexpr = RemoveRow(exitVar); pexpr.ChangeSubject(exitVar, entryVar); SubstituteOut(entryVar, pexpr); AddRow(entryVar, pexpr); } /// /// Fix the constants in the equations representing the stays. /// /// /// Each of the non-required stays will be represented by an equation /// of the form /// v = c + eplus - eminus /// where v is the variable with the stay, c is the previous value /// of v, and eplus and eminus are slack variables that hold the error /// in satisfying the stay constraint. We are about to change something, /// and we want to fix the constants in the equations representing the /// stays. If both eplus and eminus are nonbasic they have value 0 /// in the current solution, meaning the previous stay was exactly /// satisfied. In this case nothing needs to be changed. Otherwise one /// of them is basic, and the other must occur only in the expression /// for that basic error variable. Reset the constant of this /// expression to 0. /// protected void ResetStayConstants() { if (Trace) FnEnterPrint("ResetStayConstants"); for (int i = 0; i < _stayPlusErrorVars.Count; i++) { ClLinearExpression expr = RowExpression((ClAbstractVariable) _stayPlusErrorVars[i]); if (expr == null) expr = RowExpression((ClAbstractVariable) _stayMinusErrorVars[i]); if (expr != null) expr.Constant = 0.0; } } /// /// Set the external variables known to this solver to their appropriate values. /// /// /// Set each external basic variable to its value, and set each external parametric /// variable to 0. (It isn't clear that we will ever have external parametric /// variables -- every external variable should either have a stay on it, or have an /// equation that defines it in terms of other external variables that do have stays. /// For the moment I'll put this in though.) Variables that are internal to the solver /// don't actually store values -- their values are just implicit in the tableau -- so /// we don't need to set them. /// protected void SetExternalVariables() { if (Trace) FnEnterPrint("SetExternalVariables:"); if (Trace) TracePrint(this.ToString()); foreach (ClVariable v in _externalParametricVars) { if (RowExpression(v) != null) { #if !COMPACT Console.Error.WriteLine("Error: variable " + v + "in _externalParametricVars is basic"); #else Console.WriteLine("Error: variable " + v + "in _externalParametricVars is basic"); #endif continue; } v.ChangeValue(0.0); } foreach (ClVariable v in _externalRows) { ClLinearExpression expr = RowExpression(v); if (Trace) DebugPrint("v == " + v); if (Trace) DebugPrint("expr == " + expr); v.ChangeValue(expr.Constant); } _cNeedsSolving = false; } /// /// Protected convenience function to insert an error variable /// into the _errorVars set, creating the mapping with Add as necessary. /// protected void InsertErrorVar(ClConstraint cn, ClAbstractVariable var) { if (Trace) FnEnterPrint("InsertErrorVar: " + cn + ", " + var); Set cnset = (Set) _errorVars[cn]; if (cnset == null) _errorVars.Add(cn, cnset = new Set()); cnset.Add(var); } //// BEGIN PRIVATE INSTANCE FIELDS //// /// /// The array of negative error vars for the stay constraints /// (need both positive and negative since they have only non-negative /// values). /// private ArrayList _stayMinusErrorVars; /// /// The array of positive error vars for the stay constraints /// (need both positive and negative since they have only non-negative /// values). /// private ArrayList _stayPlusErrorVars; /// /// Give error variables for a non-required constraints, /// maps to ClSlackVariable-s. /// /// /// Map ClConstraint to Set (of ClVariable). /// private Hashtable _errorVars; /// /// Return a lookup table giving the marker variable for /// each constraints (used when deleting a constraint). /// /// /// Map ClConstraint to ClVariable. /// private Hashtable _markerVars; private ClObjectiveVariable _objective; /// /// Map edit variables to ClEditInfo-s. /// /// /// ClEditInfo instances contain all the information for an /// edit constraints (the edit plus/minus vars, the index [for old-style /// resolve(ArrayList...)] interface), and the previous value. /// (ClEditInfo replaces the parallel vectors from the Smalltalk impl.) /// private Hashtable _editVarMap; private long _slackCounter; private long _artificialCounter; private long _dummyCounter; private ArrayList _resolve_pair; private double _epsilon; private bool _cOptimizeAutomatically; private bool _cNeedsSolving; private Stack _stkCedcns; } }