/*
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;
namespace Cassowary
{
public class ClLinearExpression : Cl, ICloneable
{
public ClLinearExpression(ClAbstractVariable clv, double value, double constant)
{
if (Cl.GC)
{
#if !COMPACT
Console.Error.WriteLine("new ClLinearExpression");
#else
Console.WriteLine("new ClLinearExpression");
#endif
}
_constant = new ClDouble(constant);
_terms = new Hashtable(1);
if (clv != null)
_terms.Add(clv, new ClDouble(value));
}
public ClLinearExpression(double num) : this(null, 0, num)
{}
public ClLinearExpression() : this(0)
{}
public ClLinearExpression(ClAbstractVariable clv, double value) : this(clv, value, 0.0)
{}
public ClLinearExpression(ClAbstractVariable clv) : this(clv, 1, 0)
{}
///
/// For use by the clone method.
///
protected ClLinearExpression(ClDouble constant, Hashtable terms)
{
if (Cl.GC)
{
#if !COMPACT
Console.Error.WriteLine("clone ClLinearExpression");
#else
Console.WriteLine("clone ClLinearExpression");
#endif
}
_constant = (ClDouble) constant.Clone();
_terms = new Hashtable();
// need to unalias the ClDouble-s that we clone (do a deep clone)
foreach (ClAbstractVariable clv in terms.Keys)
{
_terms.Add(clv, ((ClDouble) terms[clv]).Clone());
}
}
public ClLinearExpression MultiplyMe(double x)
{
_constant.Value = _constant.Value * x;
foreach (ClAbstractVariable clv in _terms.Keys)
{
ClDouble cld = (ClDouble) _terms[clv];
cld.Value = cld.Value * x;
}
return this;
}
public virtual object Clone()
{
return new ClLinearExpression(_constant, _terms);
}
public /*sealed*/ ClLinearExpression Times(double x)
{
return ((ClLinearExpression) Clone()).MultiplyMe(x);
}
public /*sealed*/ ClLinearExpression Times(ClLinearExpression expr)
/*throws ExCLNonlinearExpression*/
{
if (IsConstant)
{
return expr.Times(_constant.Value);
}
else if (!expr.IsConstant)
{
throw new ExClNonlinearExpression();
}
return Times(expr._constant.Value);
}
public /*sealed*/ ClLinearExpression Plus(ClLinearExpression expr)
{
return ((ClLinearExpression) Clone()).AddExpression(expr, 1.0);
}
public /*sealed*/ ClLinearExpression Plus(ClVariable var)
/*throws ExCLNonlinearExpression*/
{
return ((ClLinearExpression) Clone()).AddVariable(var, 1.0);
}
public /*sealed*/ ClLinearExpression Minus(ClLinearExpression expr)
{
return ((ClLinearExpression) Clone()).AddExpression(expr, -1.0);
}
public /*sealed*/ ClLinearExpression Minus(ClVariable var)
/*throws ExCLNonlinearExpression*/
{
return ((ClLinearExpression) Clone()).AddVariable(var, -1.0);
}
public /*sealed*/ ClLinearExpression Divide(double x)
/*throws ExCLNonlinearExpression*/
{
if (Cl.Approx(x, 0.0))
{
throw new ExClNonlinearExpression();
}
return Times(1.0 / x);
}
public /*sealed*/ ClLinearExpression Divide(ClLinearExpression expr)
/*throws ExCLNonlinearExpression*/
{
if (!expr.IsConstant)
{
throw new ExClNonlinearExpression();
}
return Divide(expr._constant.Value);
}
public /*sealed*/ ClLinearExpression DivFrom(ClLinearExpression expr)
/*throws ExCLNonlinearExpression*/
{
if (!IsConstant || Cl.Approx(_constant.Value, 0.0))
{
throw new ExClNonlinearExpression();
}
return expr.Divide(_constant.Value);
}
public /*sealed*/ ClLinearExpression SubtractFrom(ClLinearExpression expr)
{
return expr.Minus(this);
}
///
/// Add n*expr to this expression from another expression expr.
/// Notify the solver if a variable is added or deleted from this
/// expression.
///
public /*sealed*/ ClLinearExpression AddExpression(ClLinearExpression expr, double n,
ClAbstractVariable subject, ClTableau solver)
{
IncrementConstant(n * expr.Constant);
foreach (ClAbstractVariable clv in expr.Terms.Keys)
{
double coeff = ((ClDouble) expr.Terms[clv]).Value;
AddVariable(clv, coeff * n, subject, solver);
}
return this;
}
///
/// Add n*expr to this expression from another expression expr.
///
public /*sealed*/ ClLinearExpression AddExpression(ClLinearExpression expr, double n)
{
IncrementConstant(n * expr.Constant);
foreach (ClAbstractVariable clv in expr.Terms.Keys)
{
double coeff = ((ClDouble) expr.Terms[clv]).Value;
AddVariable(clv, coeff * n);
}
return this;
}
public /*sealed*/ ClLinearExpression AddExpression(ClLinearExpression expr)
{
return AddExpression(expr, 1.0);
}
///
/// Add a term c*v to this expression. If the expression already
/// contains a term involving v, add c to the existing coefficient.
/// If the new coefficient is approximately 0, delete v.
///
public /*sealed*/ ClLinearExpression AddVariable(ClAbstractVariable v, double c)
{
// body largely duplicated below
if (Trace)
FnEnterPrint(string.Format("AddVariable: {0}, {1}", v, c));
ClDouble coeff = (ClDouble) _terms[v];
if (coeff != null)
{
double new_coefficient = coeff.Value + c;
if (Cl.Approx(new_coefficient, 0.0))
{
_terms.Remove(v);
}
else
{
coeff.Value = new_coefficient;
}
}
else
{
if (!Cl.Approx(c, 0.0))
{
_terms.Add(v, new ClDouble(c));
}
}
return this;
}
public /*sealed*/ ClLinearExpression AddVariable(ClAbstractVariable v)
{
return AddVariable(v, 1.0);
}
public /*sealed*/ ClLinearExpression SetVariable(ClAbstractVariable v, double c)
{
// Assert(c != 0.0);
ClDouble coeff = (ClDouble) _terms[v];
if (coeff != null)
coeff.Value = c;
else
_terms.Add(v, new ClDouble(c));
return this;
}
///
/// Add a term c*v to this expression. If the expression already
/// contains a term involving v, add c to the existing coefficient.
/// If the new coefficient is approximately 0, delete v. Notify the
/// solver if v appears or disappears from this expression.
///
public /*sealed*/ ClLinearExpression AddVariable(ClAbstractVariable v, double c,
ClAbstractVariable subject, ClTableau solver)
{
// body largely duplicated above
if (Trace)
FnEnterPrint(string.Format("AddVariable: {0}, {1}, {2}, ...", v, c, subject));
ClDouble coeff = (ClDouble) _terms[v];
if (coeff != null)
{
double new_coefficient = coeff.Value + c;
if (Cl.Approx(new_coefficient, 0.0))
{
solver.NoteRemovedVariable(v, subject);
_terms.Remove(v);
}
else
{
coeff.Value = new_coefficient;
}
}
else
{
if (!Cl.Approx(c, 0.0))
{
_terms.Add(v, new ClDouble(c));
solver.NoteAddedVariable(v, subject);
}
}
return this;
}
///
/// Return a pivotable variable in this expression. (It is an error
/// if this expression is constant -- signal ExCLInternalError in
/// that case). Return null if no pivotable variables
///
public /*sealed*/ ClAbstractVariable AnyPivotableVariable()
/*throws ExCLInternalError*/
{
if (IsConstant)
{
throw new ExClInternalError("anyPivotableVariable called on a constant");
}
foreach (ClAbstractVariable clv in _terms.Keys)
{
if (clv.IsPivotable)
return clv;
}
// No pivotable variables, so just return null, and let the caller
// error if needed
return null;
}
///
/// Replace var with a symbolic expression expr that is equal to it.
/// If a variable has been added to this expression that wasn't there
/// before, or if a variable has been dropped from this expression
/// because it now has a coefficient of 0, inform the solver.
/// PRECONDITIONS:
/// var occurs with a non-zero coefficient in this expression.
///
public /*sealed*/ void SubstituteOut(ClAbstractVariable var, ClLinearExpression expr,
ClAbstractVariable subject, ClTableau solver)
{
if (Trace)
FnEnterPrint(string.Format("CLE:SubstituteOut: {0}, {1}, {2}, ...", var, expr, subject));
if (Trace)
TracePrint("this = " + this);
double multiplier = ((ClDouble) _terms[var]).Value;
_terms.Remove(var);
IncrementConstant(multiplier * expr.Constant);
foreach (ClAbstractVariable clv in expr.Terms.Keys)
{
double coeff = ((ClDouble) expr.Terms[clv]).Value;
ClDouble d_old_coeff = (ClDouble) _terms[clv];
if (d_old_coeff != null)
{
double old_coeff = d_old_coeff.Value;
double newCoeff = old_coeff + multiplier * coeff;
if (Cl.Approx(newCoeff, 0.0))
{
solver.NoteRemovedVariable(clv, subject);
_terms.Remove(clv);
}
else
{
d_old_coeff.Value = newCoeff;
}
}
else
{
// did not have that variable already
_terms.Add(clv, new ClDouble(multiplier * coeff));
solver.NoteAddedVariable(clv, subject);
}
}
if (Trace)
TracePrint("Now this is " + this);
}
///
/// This linear expression currently represents the equation
/// oldSubject=self. Destructively modify it so that it represents
/// the equation newSubject=self.
///
/// Precondition: newSubject currently has a nonzero coefficient in
/// this expression.
///
/// NOTES
/// Suppose this expression is c + a*newSubject + a1*v1 + ... + an*vn.
///
/// Then the current equation is
/// oldSubject = c + a*newSubject + a1*v1 + ... + an*vn.
/// The new equation will be
/// newSubject = -c/a + oldSubject/a - (a1/a)*v1 - ... - (an/a)*vn.
/// Note that the term involving newSubject has been dropped.
///
public /*sealed*/ void ChangeSubject(ClAbstractVariable old_subject, ClAbstractVariable new_subject)
{
ClDouble cld = (ClDouble) _terms[old_subject];
if (cld != null)
cld.Value = NewSubject(new_subject);
else
_terms.Add(old_subject, new ClDouble(NewSubject(new_subject)));
}
///
/// This linear expression currently represents the equation self=0. Destructively modify it so
/// that subject=self represents an equivalent equation.
///
/// Precondition: subject must be one of the variables in this expression.
/// NOTES
/// Suppose this expression is
/// c + a*subject + a1*v1 + ... + an*vn
/// representing
/// c + a*subject + a1*v1 + ... + an*vn = 0
/// The modified expression will be
/// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
/// representing
/// subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
///
/// Note that the term involving subject has been dropped.
/// Returns the reciprocal, so changeSubject can use it, too
///
public /*sealed*/ double NewSubject(ClAbstractVariable subject)
{
if (Trace)
FnEnterPrint(string.Format("newSubject: {0}", subject));
ClDouble coeff = (ClDouble) _terms[subject];
_terms.Remove(subject);
double reciprocal = 1.0 / coeff.Value;
MultiplyMe(-reciprocal);
return reciprocal;
}
///
/// Return the coefficient corresponding to variable var, i.e.,
/// the 'ci' corresponding to the 'vi' that var is:
/// v1*c1 + v2*c2 + .. + vn*cn + c
///
public /*sealed*/ double CoefficientFor(ClAbstractVariable var)
{
ClDouble coeff = (ClDouble) _terms[var];
if (coeff != null)
return coeff.Value;
else
return 0.0;
}
public double Constant
{
get {
return _constant.Value;
}
set {
_constant.Value = value;
}
}
public Hashtable Terms
{
get {
return _terms;
}
}
public /*sealed*/ void IncrementConstant(double c)
{
_constant.Value = _constant.Value + c;
}
public bool IsConstant
{
get {
return _terms.Count == 0;
}
}
public override string ToString()
{
String s = "";
IDictionaryEnumerator e = _terms.GetEnumerator();
if (!Cl.Approx(_constant.Value, 0.0) || _terms.Count == 0)
{
s += _constant.ToString();
}
else
{
if (_terms.Count == 0)
{
return s;
}
e.MoveNext(); // go to first element
ClAbstractVariable clv = (ClAbstractVariable) e.Key;
ClDouble coeff = (ClDouble) _terms[clv];
s += string.Format("{0}*{1}", coeff.ToString(), clv.ToString());
}
while (e.MoveNext())
{
ClAbstractVariable clv = (ClAbstractVariable) e.Key;
ClDouble coeff = (ClDouble) _terms[clv];
s += string.Format(" + {0}*{1}", coeff.ToString(), clv.ToString());
}
return s;
}
public /*sealed*/ new static ClLinearExpression Plus(ClLinearExpression e1, ClLinearExpression e2)
{
return e1.Plus(e2);
}
public /*sealed*/ new static ClLinearExpression Minus(ClLinearExpression e1, ClLinearExpression e2)
{
return e1.Minus(e2);
}
public /*sealed*/ new static ClLinearExpression Times(ClLinearExpression e1, ClLinearExpression e2)
/* throws ExCLNonlinearExpression */
{
return e1.Times(e2);
}
public /*sealed*/ new static ClLinearExpression Divide(ClLinearExpression e1, ClLinearExpression e2)
/* throws ExCLNonlinearExpression */
{
return e1.Divide(e2);
}
public /*sealed*/ static bool FEquals(ClLinearExpression e1, ClLinearExpression e2)
{
return e1 == e2;
}
private ClDouble _constant;
private Hashtable _terms; // from ClVariable to ClDouble
}
}