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}