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 }