001/** An algorithm to solve a set of inequality constraints. 002 003 Copyright (c) 1997-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 added description() method 028 made many methods throw IllegalActionException 029 030 */ 031package ptolemy.graph; 032 033import java.util.ArrayList; 034import java.util.Enumeration; 035import java.util.Hashtable; 036import java.util.Iterator; 037import java.util.LinkedList; 038 039import ptolemy.kernel.util.IllegalActionException; 040import ptolemy.kernel.util.InvalidStateException; 041import ptolemy.kernel.util.NamedObj; 042 043/////////////////////////////////////////////////////////////////// 044//// InequalitySolver 045 046/** 047 An algorithm to solve a set of inequality constraints. 048 049 This algorithm is based on J. Rehof and T. Mogensen, "Tractable 050 Constraints in Finite Semilattices," Third International Static Analysis 051 Symposium, pp. 285-301, Vol 1145 of Lecture Notes in Computer Science, 052 Springer, Sept., 1996.<p> 053 054 The algorithm in Rehof works for definite inequalities. This 055 class does not enforce this requirement. However, if the inequalities 056 are not definite, this solver may not be able to find the solution even 057 when the set of inequalities is satisfiable. See the above paper for 058 details.<p> 059 060 This solver supports finding both the least and greatest solutions (if 061 they exist). It assumes that the CPO passed to the constructor is a 062 lattice, but it does not verify it. If the algorithm finds that the 063 LUB or GLB of some elements does not exist, an Exception is thrown. 064 065 @author Yuhong Xiong 066 @version $Id$ 067 @since Ptolemy II 0.2 068 @Pt.ProposedRating Green (cxh) 069 @Pt.AcceptedRating Red (cxh) 070 */ 071 072// Note: To make it easier to reference the above paper, some of the 073// private methods and variables in this class have the same names that 074// are used in the paper, which may violate the naming convention 075// in some cases. 076public class InequalitySolver { 077 /** Construct an inequality solver. 078 * @param cpo The CPO over which the inequalities are defined. 079 */ 080 public InequalitySolver(CPO cpo) { 081 _cpo = cpo; 082 } 083 084 /////////////////////////////////////////////////////////////////// 085 //// public methods //// 086 087 /** Add a group of inequalities to the set of constraints. 088 * @param inequalities An <code>Iterator</code> for instances of 089 * <code>Inequality</code>. 090 */ 091 public void addInequalities(Iterator inequalities) { 092 while (inequalities.hasNext()) { 093 addInequality((Inequality) inequalities.next()); 094 } 095 } 096 097 /** Add an <code>Inequality</code> to the set of constraints. 098 * @param ineq An <code>Inequality</code>. 099 */ 100 public void addInequality(Inequality ineq) { 101 // put ineq. to _Ilist 102 Integer indexWrap = Integer.valueOf(_Ilist.size()); 103 Info info = new Info(ineq); 104 _Ilist.add(info); 105 106 // add var->ineq to Hashtable 107 _addToClist(ineq.getLesserTerm().getVariables(), indexWrap); 108 _addToClist(ineq.getGreaterTerm().getVariables(), indexWrap); 109 } 110 111 /** Return an <code>Iterator</code> of the variables whose current 112 * values are the bottom of the underlying CPO. If none of the 113 * variables have its current value set to the bottom, an empty 114 * <code>Iterator</code> is returned. 115 * @return An Iterator of InequalityTerms 116 * @exception InvalidStateException If the underlying CPO does not 117 * have a bottom element. 118 * @exception IllegalActionException If testing any one of the 119 * variables throws an exception. 120 */ 121 public Iterator bottomVariables() throws IllegalActionException { 122 Object bottom = _cpo.bottom(); 123 124 if (bottom == null) { 125 throw new InvalidStateException( 126 "The underlying CPO does not have a bottom element."); 127 } 128 129 return _filterVariables(bottom); 130 } 131 132 /** Return a description of this solver as a String. 133 * @return A description of this solver. 134 */ 135 public String description() { 136 // This method is useful for debugging. 137 StringBuffer results = new StringBuffer("{_Ilist:\n "); 138 139 for (int i = 0; i < _Ilist.size(); i++) { 140 Info info = (Info) _Ilist.get(i); 141 results.append("{_ineq: " + info._ineq + " _inCvar: " + info._inCvar 142 + " _inserted: " + info._inserted + "}\n "); 143 } 144 145 results.append("}\n{Clist:\n "); 146 147 for (Enumeration e = _Clist.keys(); e.hasMoreElements();) { 148 InequalityTerm variable = (InequalityTerm) e.nextElement(); 149 results.append("{" + (variable == null ? "variable == null" 150 : variable.toString()) + "}\n "); 151 } 152 153 results.append("}\n"); 154 return results.toString(); 155 } 156 157 /** Solve the set of inequalities for the greatest solution. 158 * If the set of inequalities is definite (when solving for the greatest 159 * solution, <i>definite</i> means that the lesser terms of all the 160 * inequalities are either constants or single variables), 161 * this method can always determine satisfiability. In this case, if 162 * the set of inequalities is satisfiable, this method returns 163 * <code>true</code>, and the variables are set to the greatest solution. 164 * If the set of inequalities is not satisfiable, this method returns 165 * <code>false</code>. 166 * <p> 167 * If the set of inequalities is not definite, this method cannot 168 * always determine satisfiability. In this case, if the set of 169 * inequalities is satisfiable, this method may or may not return 170 * <code>true</code>. If this method returns <code>true</code>, 171 * the variables are set to the greatest solution. If the set of 172 * inequalities is not satisfiable, this method returns 173 * <code>false</code>. 174 * <p> 175 * In any case, if this method returns <code>false</code>, the 176 * variables are set to the greatest solution for the subset of 177 * inequalities whose lesser terms are a single variable. 178 * See the paper referred in the class document for details. 179 * @return True if a solution for the inequalities is found, 180 * false otherwise. 181 * @exception IllegalActionException If testing any one of the 182 * inequalities throws an exception. 183 */ 184 public boolean solveGreatest() throws IllegalActionException { 185 return _solve(false); 186 } 187 188 /** Solve the set of inequalities for the least solution. 189 * If the set of inequalities is definite (when solving for the least 190 * solution, <i>definite</i> means that the greater terms of all the 191 * inequalities are either constants or single variables), 192 * this method can always determine satisfiability. In this case, if 193 * the set of inequalities is satisfiable, this method returns 194 * <code>true</code>, and the variables are set to the least solution. 195 * If the set of inequalities is not satisfiable, this method returns 196 * <code>false</code>. 197 * <p> 198 * If the set of inequalities is not definite, this method cannot 199 * always determine satisfiability. In this case, if the set of 200 * inequalities is satisfiable, this method may or may not return 201 * <code>true</code>. If this method returns <code>true</code>, 202 * the variables are set to the least solution. If the set of 203 * inequalities is not satisfiable, this method returns 204 * <code>false</code>. 205 * <p> 206 * In any case, if this method returns <code>false</code>, the 207 * variables are set to the least solution for the subset of 208 * inequalities whose greater terms are a single variable. 209 * See the paper referred to in the class document for details. 210 * @return True if a solution for the inequalities is found, 211 * <code>false</code> otherwise. 212 * @exception IllegalActionException If testing any one of the 213 * inequalities throws an exception. 214 */ 215 public boolean solveLeast() throws IllegalActionException { 216 return _solve(true); 217 } 218 219 /** Return an <code>Iterator</code> of the variables whose current 220 * values are the top of the underlying CPO. If none of the 221 * variables have the current value set to the top, an empty 222 * <code>Iterator</code> is returned. 223 * @return An Iterator of InequalityTerms 224 * @exception InvalidStateException If the underlying CPO does not 225 * have a top element. 226 * @exception IllegalActionException If testing any one of the 227 * variables throws an exception. 228 */ 229 public Iterator topVariables() throws IllegalActionException { 230 Object top = _cpo.top(); 231 232 if (top == null) { 233 throw new InvalidStateException( 234 "The underlying CPO does not have a top element."); 235 } 236 237 return _filterVariables(top); 238 } 239 240 /** Return an <code>Iterator</code> of <code>Inequalities</code> 241 * that are not satisfied with the current value of variables. 242 * If all the inequalities are satisfied, an empty 243 * <code>Iterator</code> is returned. 244 * @return An Iterator of Inequalities 245 * @exception IllegalActionException If testing any one of the 246 * inequalities throws an exception. 247 */ 248 public Iterator unsatisfiedInequalities() throws IllegalActionException { 249 LinkedList result = new LinkedList(); 250 251 for (int i = 0; i < _Ilist.size(); i++) { 252 Info info = (Info) _Ilist.get(i); 253 254 if (!info._ineq.isSatisfied(_cpo)) { 255 result.addLast(info._ineq); 256 } 257 } 258 259 return result.iterator(); 260 } 261 262 /** Return an <code>Iterator</code> of all the variables in the 263 * inequality constraints. 264 * @return An Iterator of InequalityTerms 265 */ 266 public Iterator variables() { 267 LinkedList result = new LinkedList(); 268 269 for (Enumeration e = _Clist.keys(); e.hasMoreElements();) { 270 InequalityTerm variable = (InequalityTerm) e.nextElement(); 271 result.addLast(variable); 272 } 273 274 return result.iterator(); 275 } 276 277 /////////////////////////////////////////////////////////////////// 278 //// inner class //// 279 // Each instance of this class is an entry in _Ilist. 280 private static class Info { 281 282 // FindBugs suggests making this class static so as to decrease 283 // the size of instances and avoid dangling references. 284 285 private Info(Inequality ineq) { 286 _ineq = ineq; 287 } 288 289 private Inequality _ineq; 290 291 // True if this ineq. is in the "Cvar" set of the Rehof paper, 292 // i.e., if looking for the least solution and the greaterTerm 293 // is settable, or looking for the greatest solution and the 294 // lesserTerm is settable. 295 private boolean _inCvar = false; 296 297 // If this ineq. is in _NS 298 private boolean _inserted = false; 299 } 300 301 /////////////////////////////////////////////////////////////////// 302 //// private methods //// 303 // Add the InequalityTerms in the specified array as keys and the 304 // index as value to _Clist. The InequalityTerms are variables 305 // and the index is the index of the Inequality in _Ilist that 306 // contains the variables. 307 private void _addToClist(InequalityTerm[] variables, Integer indexWrap) { 308 for (int i = 0; i < variables.length; i++) { 309 if (!variables[i].isSettable()) { 310 Object variableValue = null; 311 try { 312 variableValue = variables[i].getValue(); 313 } catch (IllegalActionException ex) { 314 variableValue = ex.toString(); 315 } 316 Object variableObject = null; 317 try { 318 variableObject = variables[i].getAssociatedObject(); 319 if (variableObject instanceof NamedObj) { 320 variableObject = ((NamedObj) variableObject) 321 .getFullName(); 322 } 323 } catch (Exception ex) { 324 variableObject = ex.toString(); 325 } 326 throw new InvalidStateException("Port \" " + variableObject 327 + "\" of type \"" + variableValue 328 + "\" in an InequalityTerm is not settable." 329 + " If the port is an input and has a type constraint," 330 + " try removing the type constraint and possibly" 331 + " placing it on the output."); 332 } 333 ArrayList entry = (ArrayList) _Clist.get(variables[i]); 334 335 if (entry == null) { 336 // variable not in Hashtable 337 entry = new ArrayList(); 338 _Clist.put(variables[i], entry); 339 } 340 341 entry.add(indexWrap); 342 } 343 } 344 345 // filter out the variables with a certain value. If the given value 346 // is null, return all variables. This method is used by, 347 // bottomVariables(), and topVariables(), and variables(). For variables(), 348 // this method effectively converts an Enumeration to an Iterator. 349 // This is necessary for interface consistency since other methods 350 // in this package return Iterators. 351 private Iterator _filterVariables(Object value) 352 throws IllegalActionException { 353 LinkedList result = new LinkedList(); 354 355 for (Enumeration e = _Clist.keys(); e.hasMoreElements();) { 356 InequalityTerm variable = (InequalityTerm) e.nextElement(); 357 358 if (value == null || variable.getValue().equals(value)) { 359 result.addLast(variable); 360 } 361 } 362 363 return result.iterator(); 364 } 365 366 // The solver used by solveLeast() and solveGreatest(). 367 // If the argument is true, solve for the least solution; 368 // otherwise, solve for the greatest solution. 369 private boolean _solve(boolean least) throws IllegalActionException { 370 // initialize all variables 371 Object init = least ? _cpo.bottom() : _cpo.top(); 372 373 if (init == null) { 374 throw new InvalidStateException( 375 "The underlying CPO is not a lattice because " 376 + "the CPO has no " + (least ? "bottom" : "top") 377 + ". The CPO was a " + _cpo.getClass().getName()); 378 379 } 380 381 for (Enumeration e = _Clist.keys(); e.hasMoreElements();) { 382 InequalityTerm variable = (InequalityTerm) e.nextElement(); 383 384 try { 385 variable.initialize(init); 386 } catch (IllegalActionException ex) { 387 throw new InvalidStateException(null, null, ex, 388 "Cannot initialize variable."); 389 } 390 } 391 392 // initialize _NS(not satisfied) list; set _inCvar and _inserted flags. 393 // Not Satisfied list. Each entry is an Integer storing index to 394 // _Ilist. 395 // Note: removal in jdk1.2 LinkedList is not an O(1) operation, but 396 // an O(n) operation, where n is the number of elements in list. 397 // If the size of _NS is large, writing our own linked list class 398 // with a Cell class might be better. 399 LinkedList _NS = new LinkedList(); 400 401 for (int i = 0; i < _Ilist.size(); i++) { 402 Info info = (Info) _Ilist.get(i); 403 info._inCvar = least ? info._ineq.getGreaterTerm().isSettable() 404 : info._ineq.getLesserTerm().isSettable(); 405 406 if (info._inCvar) { 407 if (info._ineq.isSatisfied(_cpo)) { 408 info._inserted = false; 409 } else { // insert to _NS 410 _NS.addLast(Integer.valueOf(i)); 411 info._inserted = true; 412 } 413 } 414 } 415 416 // The outer loop is for handling the situation that some 417 // InequalityTerms do not report all the variables they depend on 418 // from the getVariables() call. This can happen, for example, in 419 // type resolution application involving structured types, where 420 // the type term for an element of a structured type does not have 421 // a reference to the term of its enclosing type. 422 // This occurs in feedback loops with structure types. 423 boolean allSatisfied = false; 424 425 LinkedList prevNS = null; 426 int loopCnt = 0; 427 while (!allSatisfied) { 428 // solve the inequalities 429 while (_NS.size() > 0) { 430 int index = ((Integer) _NS.removeFirst()).intValue(); 431 432 Info info = (Info) _Ilist.get(index); 433 info._inserted = false; 434 435 Object value = null; 436 InequalityTerm updateTerm = null; 437 438 if (least) { 439 updateTerm = info._ineq.getGreaterTerm(); 440 value = _cpo.leastUpperBound( 441 info._ineq.getLesserTerm().getValue(), 442 updateTerm.getValue()); 443 } else { 444 updateTerm = info._ineq.getLesserTerm(); 445 value = _cpo.greatestLowerBound(updateTerm.getValue(), 446 info._ineq.getGreaterTerm().getValue()); 447 } 448 449 if (value == null) { 450 throw new InvalidStateException("The CPO over which " 451 + "the inequalities are defined is not a lattice."); 452 } 453 454 try { 455 updateTerm.setValue(value); 456 } catch (IllegalActionException ex) { 457 throw new InvalidStateException(null, null, ex, 458 "Can't update variable.\n"); 459 } 460 461 // insert or drop the inequalities affected 462 ArrayList affected = (ArrayList) _Clist.get(updateTerm); 463 464 for (int i = 0; i < affected.size(); i++) { 465 Integer index1Wrap = (Integer) affected.get(i); 466 int index1 = index1Wrap.intValue(); 467 Info affectedInfo = (Info) _Ilist.get(index1); 468 469 if (index1 != index && affectedInfo._inCvar) { 470 if (affectedInfo._ineq.isSatisfied(_cpo)) { // drop 471 472 if (affectedInfo._inserted) { 473 _NS.remove(index1Wrap); 474 } 475 } else { // insert 476 477 if (!affectedInfo._inserted) { 478 _NS.addFirst(index1Wrap); 479 } 480 } 481 } 482 } 483 } 484 485 allSatisfied = true; 486 487 for (int i = 0; i < _Ilist.size(); i++) { 488 Info info = (Info) _Ilist.get(i); 489 490 if (info._inCvar) { 491 if (info._ineq.isSatisfied(_cpo)) { 492 info._inserted = false; 493 } else { // insert to _NS 494 _NS.addLast(Integer.valueOf(i)); 495 info._inserted = true; 496 allSatisfied = false; 497 } 498 } 499 } 500 501 // Avoid infinite loops. 502 // The issue is that in feedback loops, we may have arrays of arrays or arrays, 503 // forever. We have to truncate the search at some point, so we limit the depth. 504 if (prevNS == null) { 505 prevNS = _NS; 506 } else if (_NS.size() > 0 && prevNS.size() == _NS.size() 507 && prevNS.containsAll(_NS) && loopCnt > _DEPTH_LIMIT) { 508 StringBuffer errorMessage = new StringBuffer(); 509 for (Object o : _NS) { 510 Integer i = (Integer) o; 511 errorMessage.append(" (" 512 + ((Info) _Ilist.get(i))._ineq.getGreaterTerm() 513 + " >= " 514 + ((Info) _Ilist.get(i))._ineq.getLesserTerm() 515 + ") "); 516 } 517 throw new IllegalActionException( 518 "Cannot resolve types. Unsatisfied constraints: " 519 + errorMessage); 520 } 521 loopCnt++; 522 } 523 524 // Check the inequalities not involved in the above iteration. 525 // These inequalities are the ones in the "Ccnst" set in the 526 // Rehof paper. 527 for (int i = 0; i < _Ilist.size(); i++) { 528 Info info = (Info) _Ilist.get(i); 529 530 if (!info._inCvar) { 531 if (!info._ineq.isSatisfied(_cpo)) { 532 return false; 533 } 534 } 535 } 536 537 return true; 538 } 539 540 /////////////////////////////////////////////////////////////////// 541 //// private variables //// 542 private CPO _cpo = null; 543 544 // ArrayList representation of Ilist. Each entry is an instance of the 545 // inner class Info. This vector effectively gives each inequality an 546 // index, _Clist and _NS use that index. 547 private ArrayList _Ilist = new ArrayList(); 548 549 // Mapping from variable to the Inequalities containing them. 550 // Each entry in _Clist is a vector of Integers containing the 551 // index of inequalities in _Ilist. 552 private Hashtable _Clist = new Hashtable(); 553 554 /** Limit the depth of arrays of arrays tolerated by type inferences. */ 555 private static int _DEPTH_LIMIT = 1000; 556}