1 module cassowary.LinearExpression;
2 
3 import std.stdio;
4 import std.conv;
5 import std.math;
6 
7 import cassowary.AbstractVariable;
8 import cassowary.Variable;
9 import cassowary.Tableau;
10 import cassowary.Cl;
11 import cassowary.Error;
12 
13 class ClLinearExpression
14 {
15 	this(ClAbstractVariable clv, double value = 1, double constant = 0)
16 	{
17 		_constant = constant;
18 		if (clv !is null)
19 			_terms[clv] = value;
20 	}
21 
22 	this(double num = 0)
23 	{
24 		this(null, 0, num);
25 	}
26 
27 	// for use by the clone method
28 	private this(double constant, double[ClAbstractVariable] terms)
29 	{
30 		_constant = constant;
31 		_terms = terms.dup();
32 	}
33 
34 	ClLinearExpression multiplyMe(double x)
35 	{
36 		_constant *= x;
37 
38 		foreach(ClAbstractVariable clv, ref val; _terms)
39 		{
40 			val *= x;
41 		}
42 		return this;
43 	}
44 
45 	final Object clone()
46 	{
47 		return new ClLinearExpression(_constant, _terms);
48 	}
49 
50 	ClLinearExpression opBinary(string op, T) (T expr)
51 	{
52 		static if (op == "+")
53 			return plus(expr);
54 		else
55 			static if (op == "-")
56 				return minus(expr);
57 			else
58 				static if (op == "*")
59 					return times(expr);
60 				else
61 					static if (op == "/")
62 						return divide(expr);
63 	}
64 
65 	final ClLinearExpression times(double x)
66 	{
67 		return (cast(ClLinearExpression) clone()).multiplyMe(x);
68 	}
69 
70 	final ClLinearExpression times(ClLinearExpression expr)
71 	{
72 		if (isConstant())
73 		{
74 			return expr.times(_constant);
75 		}
76 		else if (!expr.isConstant())
77 		{
78 			throw new ClErrorNonlinearExpression();
79 		}
80 		return times(expr._constant);
81 	}
82 
83 	final ClLinearExpression plus(double c)
84 	{
85 		ClLinearExpression result = cast(ClLinearExpression) clone();
86 		result._constant += c;
87 		return result;
88 	}
89 
90 	final ClLinearExpression plus(ClLinearExpression expr)
91 	{
92 		return (cast(ClLinearExpression) clone()).addExpression(expr, 1.0);
93 	}
94 
95 	final ClLinearExpression plus(ClVariable var)
96 	{
97 		return (cast(ClLinearExpression) clone()).addVariable(var, 1.0);
98 	}
99 
100 	final ClLinearExpression minus(double c)
101 	{
102 		return plus(-c);
103 	}
104 
105 	final ClLinearExpression minus(ClLinearExpression expr)
106 	{
107 		return (cast(ClLinearExpression) clone()).addExpression(expr, -1.0);
108 	}
109 
110 	final ClLinearExpression minus(ClVariable var)
111 	{
112 		return (cast(ClLinearExpression) clone()).addVariable(var, -1.0);
113 	}
114 
115 
116 	final ClLinearExpression divide(double x)
117 	{
118 		if (approxEqual(x, 0.0))
119 		{
120 			throw new ClErrorNonlinearExpression();
121 		}
122 		return times(1.0/x);
123 	}
124 
125 	final ClLinearExpression divide(ClLinearExpression expr)
126 	{
127 		if (!expr.isConstant())
128 		{
129 			throw new ClErrorNonlinearExpression();
130 		}
131 		return divide(expr._constant);
132 	}
133 
134 	final ClLinearExpression divFrom(ClLinearExpression expr)
135 	{
136 		if (!isConstant() || approxEqual(_constant, 0.0))
137 		{
138 			throw new ClErrorNonlinearExpression();
139 		}
140 		return expr.divide(_constant);
141 	}
142 
143 	final ClLinearExpression subtractFrom(ClLinearExpression expr)
144 	{
145 		return expr.minus( this);
146 	}
147 
148 	// Add n*expr to this expression from another expression expr.
149 	// Notify the solver if a variable is added or deleted from this
150 	// expression.
151 	final ClLinearExpression addExpression(ClLinearExpression expr, double n,
152 										   ClAbstractVariable subject,
153 										   ClTableau solver)
154 	{
155 		incrementConstant(n * expr.constant());
156 
157 		foreach(ClAbstractVariable clv, coeff; expr.terms())
158 		{
159 			addVariable(clv, coeff*n, subject, solver);
160 		}
161 		return this;
162 	}
163 
164 	// Add n*expr to this expression from another expression expr.
165 	final ClLinearExpression addExpression(ClLinearExpression expr, double n = 1)
166 	{
167 		incrementConstant(n * expr.constant());
168 
169 		foreach(ClAbstractVariable clv, coeff; expr.terms())
170 		{
171 			addVariable(clv, coeff*n);
172 		}
173 		return this;
174 	}
175 
176 	// Add a term c*v to this expression.  If the expression already
177 	// contains a term involving v, add c to the existing coefficient.
178 	// If the new coefficient is approximately 0, delete v.
179 	final ClLinearExpression addVariable(ClAbstractVariable v, double c)
180 	{ // body largely duplicated below
181 		fnenterprint("addVariable:" ~ v.toString() ~ ", " ~ c.to!string());
182 
183 		double* coeff = v in _terms;
184 		if (coeff !is null)
185 		{
186 			double new_coefficient = *coeff + c;
187 			if (approxEqual(new_coefficient, 0.0))
188 			{
189 				_terms.remove(v);
190 			}
191 			else
192 			{
193 				*coeff = new_coefficient;
194 			}
195 		}
196 		else if (!approxEqual(c, 0.0))
197 		{
198 			_terms[v] = c;
199 		}
200 		return this;
201 	}
202 
203 	final ClLinearExpression addVariable(ClAbstractVariable v)
204 	{
205 		return addVariable(v, 1.0);
206 	}
207 
208 	final ClLinearExpression setVariable(ClAbstractVariable v, double c)
209 	{
210 		//assert(c != 0.0);
211 		_terms[v] = c;
212 		return this;
213 	}
214 
215 	// Add a term c*v to this expression.  If the expression already
216 	// contains a term involving v, add c to the existing coefficient.
217 	// If the new coefficient is approximately 0, delete v.  Notify the
218 	// solver if v appears or disappears from this expression.
219 	final ClLinearExpression addVariable(ClAbstractVariable v, double c,
220 										 ClAbstractVariable subject, ClTableau solver)
221 	{  // body largely duplicated above
222 		fnenterprint("addVariable:" ~ v.toString() ~ ", " ~ c.to!string() ~ ", " ~ subject.toString() ~ ", ...");
223 
224 		double* coeff = v in _terms;
225 		if (coeff !is null)
226 		{
227 			double new_coefficient = *coeff + c;
228 			if (approxEqual(new_coefficient, 0.0))
229 			{
230 				solver.noteRemovedVariable(v, subject);
231 				_terms.remove(v);
232 			}
233 			else
234 			{
235 				*coeff = new_coefficient;
236 			}
237 		}
238 		else
239 		{
240 			if (!approxEqual(c, 0.0))
241 			{
242 				_terms[v] = c;
243 				solver.noteAddedVariable(v, subject);
244 			}
245 		}
246 		return this;
247 	}
248 
249 	// Return a pivotable variable in this expression.  (It is an error
250 	// if this expression is constant -- signal ClErrorInternal in
251 	// that case).  Return null if no pivotable variables
252 	final ClAbstractVariable anyPivotableVariable()
253 	{
254 		if (isConstant())
255 		{
256 			throw new ClErrorInternal("anyPivotableVariable called on a constant");
257 		}
258 
259 		foreach(ClAbstractVariable clv, val; _terms)
260 		{
261 			if (clv.isPivotable())
262 				return clv;
263 		}
264 
265 		// No pivotable variables, so just return null, and let the caller
266 		// error if needed
267 		return null;
268 	}
269 
270 	// Replace var with a symbolic expression expr that is equal to it.
271 	// If a variable has been added to this expression that wasn't there
272 	// before, or if a variable has been dropped from this expression
273 	// because it now has a coefficient of 0, inform the solver.
274 	// PRECONDITIONS:
275 	//   var occurs with a non-zero coefficient in this expression.
276 	final void substituteOut(ClAbstractVariable var, ClLinearExpression expr,
277 							 ClAbstractVariable subject, ClTableau solver)
278 	{
279 		fnenterprint("CLE:substituteOut: " ~ var.toString() ~ ", " ~ expr.toString() ~ ", " ~ subject.toString() ~ ", ...");
280 		traceprint("this = " ~ this.toString());
281 
282 		double multiplier = _terms[var];
283 		_terms.remove(var);
284 		incrementConstant(multiplier * expr.constant());
285 
286 		foreach(ClAbstractVariable clv, coeff; expr.terms())
287 		{
288 			double* d_old_coeff = clv in _terms;
289 			if (d_old_coeff !is null)
290 			{
291 				double old_coeff = *d_old_coeff;
292 				double newCoeff = old_coeff + multiplier * coeff;
293 				if (approxEqual(newCoeff, 0.0))
294 				{
295 					solver.noteRemovedVariable(clv, subject);
296 					_terms.remove(clv);
297 				}
298 				else
299 				{
300 					*d_old_coeff = newCoeff;
301 				}
302 			}
303 			else
304 			{
305 				// did not have that variable already
306 				_terms[clv] = multiplier * coeff;
307 				solver.noteAddedVariable(clv, subject);
308 			}
309 		}
310 		traceprint("Now this is " ~ this.toString());
311 	}
312 
313 	// This linear expression currently represents the equation
314 	// oldSubject=self.  Destructively modify it so that it represents
315 	// the equation newSubject=self.
316 	//
317 	// Precondition: newSubject currently has a nonzero coefficient in
318 	// this expression.
319 	//
320 	// NOTES
321 	//   Suppose this expression is c + a*newSubject + a1*v1 + ... + an*vn.
322 	//
323 	//   Then the current equation is
324 	//       oldSubject = c + a*newSubject + a1*v1 + ... + an*vn.
325 	//   The new equation will be
326 	//        newSubject = -c/a + oldSubject/a - (a1/a)*v1 - ... - (an/a)*vn.
327 	//   Note that the term involving newSubject has been dropped.
328 	final void changeSubject(ClAbstractVariable old_subject, ClAbstractVariable new_subject)
329 	{
330 		_terms[old_subject] = newSubject(new_subject);
331 	}
332 
333 	// This linear expression currently represents the equation self=0.  Destructively modify it so
334 	// that subject=self represents an equivalent equation.
335 	//
336 	// Precondition: subject must be one of the variables in this expression.
337 	// NOTES
338 	//   Suppose this expression is
339 	//     c + a*subject + a1*v1 + ... + an*vn
340 	//   representing
341 	//     c + a*subject + a1*v1 + ... + an*vn = 0
342 	// The modified expression will be
343 	//    subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
344 	//   representing
345 	//    subject = -c/a - (a1/a)*v1 - ... - (an/a)*vn
346 	//
347 	// Note that the term involving subject has been dropped.
348 	// Returns the reciprocal, so changeSubject can use it, too
349 	final double newSubject(ClAbstractVariable subject)
350 	{
351 		fnenterprint("newSubject:" ~ subject.toString());
352 		double coeff = _terms[subject];
353 		double reciprocal = 1.0 / coeff;
354 		multiplyMe(-reciprocal);
355 		return reciprocal;
356 	}
357 
358 	// Return the coefficient corresponding to variable var, i.e.,
359 	// the 'ci' corresponding to the 'vi' that var is:
360 	//     v1*c1 + v2*c2 + .. + vn*cn + c
361 	final double coefficientFor(ClAbstractVariable var)
362 	{
363 		double* coeff = var in _terms;
364 		return (coeff is null) ? 0.0 : *coeff;
365 	}
366 
367 	final double constant() const
368 	{
369 		return _constant;
370 	}
371 
372 	final void set_constant(double c)
373 	{
374 		_constant = c;
375 	}
376 
377 	final auto terms()
378 	{
379 		return _terms;
380 	}
381 
382 	final void incrementConstant(double c)
383 	{
384 		_constant += c;
385 	}
386 
387 	final bool isConstant() const
388 	{
389 		return _terms.length == 0;
390 	}
391 
392 	override string toString() const
393 	{
394 		string res = "";
395 
396 		if (!approxEqual(_constant, 0.0) || _terms.length == 0)
397 		{
398 			res ~= _constant.to!string();
399 		}
400 		else
401 		{
402 			if (_terms.length == 0)
403 			{
404 				return res;
405 			}
406 
407 			res ~= _terms.to!string();
408 		}
409 		return res;
410 	}
411 
412 	private double _constant;
413 	private double[ClAbstractVariable] _terms;
414 }