001/*
002 * Copyright (c) 2005 Peter Palotas, Fredrik Johansson, Einar Pehrson,
003 * Sebastian Kekkonen, Lars Magnus Lang, Malin Johansson and Sofia Nilsson
004 *
005 * This file is part of
006 * CleanSheets Extension for Assertions
007 *
008 * CleanSheets Extension for Assertions is free software; you can
009 * redistribute it and/or modify it under the terms of the GNU General Public
010 * License as published by the Free Software Foundation; either version 2 of
011 * the License, or (at your option) any later version.
012 *
013 * CleanSheets Extension for Assertions is distributed in the hope that
014 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
015 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016 * See the GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with CleanSheets Extension for Assertions; if not, write to the
020 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
021 * Boston, MA  02111-1307  USA
022 */
023package csheets.ext.assertion;
024
025import java.io.Serializable;
026import java.util.Iterator;
027import java.util.List;
028import java.util.TreeSet;
029
030/** A class representing (possibly) multiple intervals. This can be used to specify
031        any combination of values to be included in an interval. The MultiInterval is
032        built up by specifying normal intervals to be included or excluded from the
033        MultiInterval.
034        @author Peter Palotas
035        @author Fredrik Johansson
036*/
037public class MultiInterval implements Iterable<Interval>, Serializable {
038
039        /** The unique version identifier used for serialization */
040        private static final long serialVersionUID = 3311313307922046224L;
041
042        /**
043                Contains a list of the intervals included in this multi interval.
044                <p><b>Invariant:</b> intervalSet does not contain any intersecting or
045                bordering intervals. (Two bordering intervals are eg. [0 to 1[ [1 to 2]
046                which could easily be written as one interval [0 to 2].
047        */
048        private TreeSet<Interval> intervalSet;
049
050        /** Default constructor. Constructs an empty MultiInterval containing no values. Note that if the
051                first operation called on this interval after its construction is <code>exclude()</code>,
052                <b>all</b> values <b>except</b> the ones excluded will be included in this interval.
053        */
054        public MultiInterval() {
055                intervalSet = new TreeSet<Interval>();
056        }
057
058        /** Add an interval to be included in this MultiInterval.
059                @param interval An interval specifying values to be included in this MultiInterval.
060        */
061        public void include(Interval interval) {
062                if (intervalSet.isEmpty()) {
063                        intervalSet.add(interval);
064                        return;
065                }
066
067                // Check if the new interval intersects with any one already included
068                for (Iterator<Interval> iter = intervalSet.iterator(); iter.hasNext(); ) {
069                        Interval i = iter.next();
070
071                        // Try to create a union, if successful we should
072                        Interval union = Interval.union(i, interval);
073                        if (union != null) {
074                                // remove the old interval intersecting with the new one and
075                                // include the union between them instead.
076                                iter.remove();
077                                include(union);
078                                return;
079                        }
080                }
081
082                // The new interval did not intersect with any already included, just add it.
083                intervalSet.add(interval);
084        }
085
086        /** Exclude a specific interval from this MultiInterval. If this MultiInterval is
087                empty (i.e. does not include anything) when this function is called, the MultiInterval
088                will accept <i>any value except the ones included in the interval specified</i>.
089                @param interval an interval specifying which values to exclude from this MultiInterval.
090        */
091        public void exclude(Interval interval) {
092                // If list is empty, we assume we want to include all possible values
093                // except the ones specified. So we add an interval covering all
094                // values to the list.
095                if (intervalSet.isEmpty()) {
096                        intervalSet.add(new Interval(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false, false));
097                }
098
099                TreeSet<Interval> nlist = new TreeSet<Interval>();
100
101                for (Iterator<Interval> iter = intervalSet.iterator(); iter.hasNext(); ) {
102                        Interval i = iter.next();
103
104
105                        if (i.intersects(interval)) {
106                                // Check if the lower part of the interval should be included
107                                if (i.getLowerLimit() < interval.getLowerLimit() || i.getLowerLimit() == interval.getLowerLimit() &&
108                                        i.isLowerLimitClosed() && !interval.isLowerLimitClosed()) {
109                                        nlist.add(new Interval(i.getLowerLimit(), interval.getLowerLimit(), i.isLowerLimitClosed(), !interval.isLowerLimitClosed()));
110                                }
111
112                                // Check if the upper part of the interval should be included
113                                if (i.getUpperLimit() > interval.getUpperLimit() || i.getUpperLimit() == interval.getUpperLimit() &&
114                                        i.isUpperLimitClosed() && !interval.isUpperLimitClosed()) {
115                                        nlist.add(new Interval(interval.getUpperLimit(), i.getUpperLimit(), !interval.isUpperLimitClosed(), i.isUpperLimitClosed()));
116                                }
117
118                        } else {
119                                // If the interval to exclude does not intersect the current interval
120                                // we keep the current interval as is.
121                                nlist.add(i);
122                        }
123                }
124                intervalSet = nlist;
125        }
126
127        /** Check if a value is contained in this MultiInterval. <p><b>NOTE:</b> Infinity and NaN is never
128                reported to be included in a MultiInterval.
129                @param value The value to check for.
130                @return <code>true</code> if the value has been specified to be included in this
131                        MultiInterval, <code>false</code> otherwise.
132        */
133        public boolean contains(double value) {
134
135                if (Double.isNaN(value) || Double.isInfinite(value))
136                        return false;
137
138                for (Iterator<Interval> iter = intervalSet.iterator(); iter.hasNext(); ) {
139                        Interval i = iter.next();
140                        if (i.contains(value))
141                                return true;
142
143                        if (i.getUpperLimit() > value)
144                                return false;
145                }
146                return false;
147        }
148
149
150        /**
151         * Calculates a negation av a <code>MultiInterval</code>
152         * @param term is the <code>MultiInterval</code> to be negated
153         * @return the negated <code>MultiInterval</code>
154         */
155        public static MultiInterval negate(MultiInterval term) {
156                MultiInterval negation = new MultiInterval();
157                for (Interval i:term.intervalSet)
158                        negation.include(Interval.negate(i));
159                return negation;
160        }
161
162        /**
163         * Calculates the sum of two <code>MultiInterval</code>s
164         * @param term1 is the first term
165         * @param term2 is the second term
166         * @return the summarized <code>MultiInterval</code>s
167         */
168        public static MultiInterval add(MultiInterval term1, MultiInterval term2) {
169                MultiInterval sum = new MultiInterval();
170                for (Interval i1:term1.intervalSet)
171                        for (Interval i2:term2.intervalSet)
172                                sum.include(Interval.add(i1,i2));
173                return sum;
174        }
175
176        /**
177         * Calculates the difference of two <code>MultiInterval</code>s
178         * @param term1 is the first term
179         * @param term2 is the second term
180         * @return the difference of the two <code>MultiInterval</code>s
181         */
182        public static MultiInterval sub(MultiInterval term1, MultiInterval term2) {
183                MultiInterval difference = new MultiInterval();
184                for (Interval i1:term1.intervalSet)
185                        for (Interval i2:term2.intervalSet)
186                                difference.include(Interval.sub(i1,i2));
187                return difference;
188        }
189
190        /**
191         * Calculates the product of two <code>MultiInterval</code>s
192         * @param factor1 is the first factor
193         * @param factor2 is the second factor
194         * @return the product of thw two <code>MultiInterval</code>s
195         */
196        public static MultiInterval mul(MultiInterval factor1, MultiInterval factor2) {
197                MultiInterval product = new MultiInterval();
198                for (Interval i1:factor1.intervalSet)
199                        for (Interval i2:factor2.intervalSet)
200                                product.include(Interval.mul(i1,i2));
201                return product;
202        }
203
204        /**
205         * Calculates the quotient of two <code>MultiInterval</code>s
206         * @param numerator is the numerator
207         * @param denominator is the denominator
208         * @return the quotient of the two <code>MultiInterval</code>s
209         */
210        public static MultiInterval div(MultiInterval numerator, MultiInterval denominator) throws MathException {
211                MultiInterval quotient = new MultiInterval();
212                for (Interval i1:numerator.intervalSet)
213                        for (Interval i2:denominator.intervalSet)
214                                quotient.include(Interval.div(i1,i2));
215                return quotient;
216        }
217
218
219        /**
220         * Calculates the first <code>MultiInterval</code> raised to the power of the second <code>MultiInterval</code>
221         * @param base is the first <code>MultiInterval</code>
222         * @param exponent is the second <code>MultiInterval</code>
223         * @return the first <code>MultiInterval</code> raised to the power of the second <code>MultiInterval</code>
224         * @throws MathException if the first <code>MultiInterval</code> contains values below zero and the second <code>MultiInterval</code> is not build of singel integer value <code>Interval</code>s
225         */
226        public static MultiInterval pow(MultiInterval base, MultiInterval exponent) throws MathException {
227                MultiInterval product = new MultiInterval();
228                for (Interval baseI : base.intervalSet)
229                        for (Interval expI : exponent.intervalSet)
230                                product.include(Interval.pow(baseI, expI));
231                return product;
232        }
233
234
235        /**
236         * Calculates the cosine <code>MultiInterval</code> of a <code>MultiInterval</code>
237         * @param mInterval is the <code>MultiInterval</code>
238         * @return the cosine <code>MultiInterval</code> of the <code>MultiInterval</code>
239         */
240        public static MultiInterval cos(MultiInterval mInterval) {
241                MultiInterval result = new MultiInterval();
242                for (Interval i : mInterval.intervalSet)
243                        result.include(Interval.cos(i));
244                return result;
245        }
246
247        /**
248         * Calculates the sine <code>MultiInterval</code> of a <code>MultiInterval</code>
249         * @param mInterval is the <code>MultiInterval</code>
250         * @return the sine <code>MultiInterval</code> of the <code>MultiInterval</code>
251         */
252        public static MultiInterval sin(MultiInterval mInterval) {
253                MultiInterval result = new MultiInterval();
254                for (Interval i : mInterval.intervalSet)
255                        result.include(Interval.sin(i));
256                return result;
257        }
258
259        /**
260         * Calculates the tangent <code>MultiInterval</code> of a <code>MultiInterval</code>
261         * @param mInterval is the <code>MultiInterval</code>
262         * @return the tangent <code>MultiInterval</code> of the <code>MultiInterval</code>
263         */
264        public static MultiInterval tan(MultiInterval mInterval) {
265                MultiInterval result = new MultiInterval();
266                for (Interval i : mInterval.intervalSet)
267                        result.include(Interval.tan(i));
268                return result;
269        }
270
271        /**
272         * Calculates the natural logarithm of a <code>MultiInterval</code>
273         * @param mInterval is the <code>MultiInterval</code>
274         * @return the natural logarithm of the <code>MultiInterval</code>
275         * @throws MathException if the <code>MultiInterval</code> contains values below zero
276         */
277
278        public static MultiInterval ln(MultiInterval mInterval) throws MathException {
279                MultiInterval log = new MultiInterval();
280                for (Interval i:mInterval.intervalSet)
281                        log.include(Interval.ln(i));
282                return log;
283        }
284
285        /**
286         * Calculates the base 10 logarithm of a <code>MultiInterval</code>
287         * @param mInterval is the <code>MultiInterval</code>
288         * @return the base 10 logarithm of the <code>MultiInterval</code>
289         * @throws MathException if the <code>MultiInterval</code> contains values below zero
290         */
291        public static MultiInterval log10(MultiInterval mInterval) throws MathException {
292                MultiInterval log = new MultiInterval();
293                for (Interval i : mInterval.intervalSet)
294                        log.include(Interval.log10(i));
295                return log;
296        }
297
298        /**
299         * Calculates Eulers number e raised to a <code>MultiInterval</code>
300         * @param exponent is the <code>MultiInterval</code>
301         * @return Eulers number e raised to the <code>MultiInterval</code>
302         */
303        public static MultiInterval exp(MultiInterval exponent) {
304                MultiInterval exp = new MultiInterval();
305                for (Interval i : exponent.intervalSet)
306                        exp.include(Interval.exp(i));
307                return exp;
308        }
309
310        /**
311         * Calculates the square root of a <code>MultiInterval</code>
312         * @param mInterval is the <code>MultiInterval</code>
313         * @return the square root of the <code>MultiInterval</code>
314         * @throws MathException if the <code>MultiInterval</code> contains values below zero
315         */
316        public static MultiInterval sqrt(MultiInterval mInterval) throws MathException {
317                MultiInterval root = new MultiInterval();
318                for (Interval i : mInterval.intervalSet)
319                        root.include(Interval.sqrt(i));
320                return root;
321        }
322
323        /**
324         * Calculates the <code>MultiInterval</code> you get if you convert all values from <code>double</code> to <code>int</code>
325         * @param mInterval is the <code>MultiInterval</code>
326         * @return the <code>MultiInterval</code> you get if you convert the values from <code>double</code> to <code>int</code>
327         */
328        public static MultiInterval toInt(MultiInterval mInterval) {
329                MultiInterval result = new MultiInterval();
330                for (Interval i : mInterval.intervalSet)
331                        result.include(Interval.toInt(i));
332                return result;
333        }
334
335        /**
336         * Calculates the absolute value <code>MultiInterval</code> of a <code>MultiInterval</code>
337         * @param mInterval is the <code>MultiInterval</code>
338         * @return the absolute value <code>MultiInterval</code> of the <code>MultiInterval</code>
339         */
340        public static MultiInterval abs(MultiInterval mInterval) {
341                MultiInterval result = new MultiInterval();
342                for (Interval i : mInterval.intervalSet)
343                        result.include(Interval.abs(i));
344                return result;
345        }
346
347        /**
348         * Returns a <code>MultiInterval</code> holding all possible values you get from the <code>Math.random()</code> method
349         * @return a <code>MultiInterval</code> holding all possible values you get from the <code>Math.random()</code> mathod
350         */
351        public static MultiInterval rand() {
352                MultiInterval result = new MultiInterval();
353                result.include(Interval.rand());
354                return result;
355        }
356
357        /**
358         * Calculates the factorial of a <code>MultiInterval</code>
359         * @param mInterval is the <code>MultiInterval</code>
360         * @return the factorial of the <code>MultiInterval</code>
361         * @throws MathException if the <code>MultiInterval</code> contains values below zero
362         */
363        public static MultiInterval fact(MultiInterval mInterval) throws MathException {
364                MultiInterval result = new MultiInterval();
365                for (Interval i : mInterval.intervalSet)
366                        result.include(Interval.fact(i));
367                return result;
368        }
369
370        /**
371         * Calculates the sum of a <code>List</code> of <code>MultiInterval</code>s
372         * @param terms is the terms to be summarized
373         * @return the sum of the <code>MultiInterval</code>s
374         */
375        public static MultiInterval sum(List<MultiInterval> terms) {
376                MultiInterval sum = new MultiInterval();
377                Iterator<MultiInterval> iter = terms.iterator();
378                if (iter.hasNext())
379                        sum = iter.next().clone();
380                while (iter.hasNext()) {
381                        sum = MultiInterval.add(sum,iter.next());
382                }
383                return sum;
384        }
385
386        /**
387         * Calculates the average of a <code>List</code> of <code>MultiInterval</code>s
388         * @param terms is the terms
389         * @return the average of the <code>MultiInterval</code>s
390         */
391        public static MultiInterval avg(List<MultiInterval> terms) {
392                MultiInterval size = new MultiInterval();
393                size.include(new Interval(terms.size()));
394                MultiInterval sum = MultiInterval.sum(terms);
395                MultiInterval result = new MultiInterval(); // for the compiler to be happy
396                try {
397                        result = MultiInterval.div(sum,size);
398                } catch (MathException e) { // should never happen
399                        e.printStackTrace();
400                }
401                return result;
402        }
403
404
405        /** Returns an iterator over the intervals in this MultiInterval in proper sequence.
406                Note that this iterator cannot be used to modify the MultiInterval.
407                @return an iterator over the intervals in this MultiInterval in proper sequence.
408        */
409        public Iterator<Interval> iterator() {
410                return new ConstMultiIntervalIterator(intervalSet.iterator());
411        }
412
413        /** Compares the specified MultiInterval with this MultiInterval for equality.
414                @return <code>true</code> if the two intervals represent the same ranges of values,
415                                <code>false</code> otherwise.
416        */
417        public boolean equals(Object o) {
418                if (o == null)
419                        return false;
420
421                if (!(o instanceof MultiInterval))
422                        return false;
423
424                MultiInterval mi = (MultiInterval)o;
425
426                return intervalSet.equals(mi.intervalSet);
427        }
428
429        /**
430         * Makes a copy of this <code>MultiInterval</code> intance
431         */
432        public MultiInterval clone() {
433                MultiInterval newMI = new MultiInterval();
434                for (Interval i:intervalSet)
435                        newMI.include(i);
436                return newMI;
437        }
438
439
440        /** Returns a string representation of this MultiInterval. */
441        public String toString() {
442                String s = "";
443
444                for (Iterator<Interval> iter = intervalSet.iterator(); iter.hasNext(); ) {
445                        Interval i = iter.next();
446                        s += i.toString();
447                        if (iter.hasNext())
448                                s += " or ";
449                }
450                return s;
451        }
452}