Annotation of loncom/homework/math_parser/QIntervalUnion.pm, revision 1.1

1.1     ! damieng     1: # The LearningOnline Network with CAPA - LON-CAPA
        !             2: # QIntervalUnion
        !             3: #
        !             4: # Copyright (C) 2014 Michigan State University Board of Trustees
        !             5: #
        !             6: # This program is free software: you can redistribute it and/or modify
        !             7: # it under the terms of the GNU General Public License as published by
        !             8: # the Free Software Foundation, either version 3 of the License, or
        !             9: # (at your option) any later version.
        !            10: #
        !            11: # This program is distributed in the hope that it will be useful,
        !            12: # but WITHOUT ANY WARRANTY; without even the implied warranty of
        !            13: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
        !            14: # GNU General Public License for more details.
        !            15: #
        !            16: # You should have received a copy of the GNU General Public License
        !            17: # along with this program. If not, see <http://www.gnu.org/licenses/>.
        !            18: #
        !            19: 
        !            20: ##
        !            21: # A union of possibly disjoint intervals
        !            22: ##
        !            23: package Apache::math_parser::QIntervalUnion;
        !            24: 
        !            25: use strict;
        !            26: use warnings;
        !            27: use utf8;
        !            28: 
        !            29: use aliased 'Apache::math_parser::CalcException';
        !            30: use aliased 'Apache::math_parser::Quantity';
        !            31: use aliased 'Apache::math_parser::QInterval';
        !            32: use aliased 'Apache::math_parser::QIntervalUnion';
        !            33: 
        !            34: use overload
        !            35:     '""' => \&toString,
        !            36:     '+' => \&union,
        !            37:     '*' => \&qmult;
        !            38: 
        !            39: ##
        !            40: # Constructor
        !            41: # @param {QInterval[]} intervals
        !            42: ##
        !            43: sub new {
        !            44:     my $class = shift;
        !            45:     # we use an array to preserve order (of course purely for cosmetic reasons)
        !            46:     my $self = {
        !            47:         _intervals => shift,
        !            48:     };
        !            49:     bless $self, $class;
        !            50:     
        !            51:     # sanity checks
        !            52:     foreach my $inter (@{$self->intervals}) {
        !            53:         if (!$inter->isa(QInterval)) {
        !            54:             die CalcException->new("All components of the union must be intervals.");
        !            55:         }
        !            56:     }
        !            57:     if (scalar(@{$self->intervals}) > 0) {
        !            58:         my %units = %{$self->intervals->[0]->qmin->units};
        !            59:         for (my $i=1; $i < scalar(@{$self->intervals}); $i++) {
        !            60:             my $inter = $self->intervals->[$i];
        !            61:             foreach my $unit (keys %units) {
        !            62:                 if ($units{$unit} != $inter->qmin->units->{$unit}) {
        !            63:                     die CalcException->new("Different units are used in the intervals.");
        !            64:                 }
        !            65:             }
        !            66:         }
        !            67:     }
        !            68:     
        !            69:     # clone the intervals so that they can be modified independantly
        !            70:     for (my $i=0; $i < scalar(@{$self->intervals}); $i++) {
        !            71:         $self->intervals->[$i] = $self->intervals->[$i]->clone();
        !            72:     }
        !            73:     
        !            74:     # reduction to make comparisons easier
        !            75:     $self->reduce();
        !            76:     
        !            77:     return $self;
        !            78: }
        !            79: 
        !            80: # Attribute helpers
        !            81: 
        !            82: ##
        !            83: # The intervals in the interval union, in canonical form (sorted disjoint intervals)
        !            84: # @returns {QInterval[]}
        !            85: ##
        !            86: sub intervals {
        !            87:     my $self = shift;
        !            88:     return $self->{_intervals};
        !            89: }
        !            90: 
        !            91: 
        !            92: ##
        !            93: # Returns a readable view of the object
        !            94: # @returns {string}
        !            95: ##
        !            96: sub toString {
        !            97:     my ( $self ) = @_;
        !            98:     my $s = '(';
        !            99:     for (my $i=0; $i < scalar(@{$self->intervals}); $i++) {
        !           100:         $s .= $self->intervals->[$i]->toString();
        !           101:         if ($i != scalar(@{$self->intervals}) - 1) {
        !           102:             $s .= "+";
        !           103:         }
        !           104:     }
        !           105:     $s .= ')';
        !           106:     return $s;
        !           107: }
        !           108: 
        !           109: ##
        !           110: # Equality test
        !           111: # @param {QIntervalUnion|QInterval|QSet|Quantity|QVector|QMatrix} qui
        !           112: # @optional {string|float} tolerance
        !           113: # @returns {boolean}
        !           114: ##
        !           115: sub equals {
        !           116:     my ( $self, $qiu, $tolerance ) = @_;
        !           117:     if (!$qiu->isa(QIntervalUnion)) {
        !           118:         return 0;
        !           119:     }
        !           120:     if (scalar(@{$self->intervals}) != scalar(@{$qiu->intervals})) {
        !           121:         return 0;
        !           122:     }
        !           123:     foreach my $inter1 (@{$self->intervals}) {
        !           124:         my $found = 0;
        !           125:         foreach my $inter2 (@{$qiu->intervals}) {
        !           126:             if ($inter1->equals($inter2, $tolerance)) {
        !           127:                 $found = 1;
        !           128:                 last;
        !           129:             }
        !           130:         }
        !           131:         if (!$found) {
        !           132:             return 0;
        !           133:         }
        !           134:     }
        !           135:     return 1;
        !           136: }
        !           137: 
        !           138: ##
        !           139: # Compare this interval union with another one, and returns a code.
        !           140: # Returns Quantity->WRONG_TYPE if the parameter is not a QIntervalUnion
        !           141: # (this might happen if a union of disjoint intervals is compared with a simple interval).
        !           142: # @param {QIntervalUnion|QInterval|QSet|Quantity|QVector|QMatrix} qui
        !           143: # @optional {string|float} tolerance
        !           144: # @returns {int} Quantity->WRONG_TYPE|WRONG_DIMENSIONS|MISSING_UNITS|ADDED_UNITS|WRONG_UNITS|WRONG_VALUE|WRONG_ENDPOINT|IDENTICAL
        !           145: ##
        !           146: sub compare {
        !           147:     my ( $self, $qiu, $tolerance ) = @_;
        !           148:     if (!$qiu->isa(QIntervalUnion)) {
        !           149:         return Quantity->WRONG_TYPE;
        !           150:     }
        !           151:     if (scalar(@{$self->intervals}) != scalar(@{$qiu->intervals})) {
        !           152:         return Quantity->WRONG_DIMENSIONS;
        !           153:     }
        !           154:     my @codes = ();
        !           155:     foreach my $inter1 (@{$self->intervals}) {
        !           156:         my $best_code = Quantity->WRONG_TYPE;
        !           157:         foreach my $inter2 (@{$qiu->intervals}) {
        !           158:             my $code = $inter1->compare($inter2, $tolerance);
        !           159:             if ($code == Quantity->IDENTICAL) {
        !           160:                 $best_code = $code;
        !           161:                 last;
        !           162:             } elsif ($code > $best_code) {
        !           163:                 $best_code = $code;
        !           164:             }
        !           165:         }
        !           166:         if ($best_code != Quantity->IDENTICAL) {
        !           167:             return $best_code;
        !           168:         }
        !           169:     }
        !           170:     return Quantity->IDENTICAL;
        !           171: }
        !           172: 
        !           173: ##
        !           174: # Turns the internal structure into canonical form (sorted disjoint intervals)
        !           175: ##
        !           176: sub reduce {
        !           177:     my ( $self ) = @_;
        !           178:     my @intervals = @{$self->intervals}; # shallow copy (just to make the code easier to read)
        !           179:     
        !           180:     # remove empty intervals
        !           181:     for (my $i=0; $i < scalar(@intervals); $i++) {
        !           182:         my $inter = $intervals[$i];
        !           183:         if ($inter->qmin->value == $inter->qmax->value && $inter->qminopen && $inter->qmaxopen) {
        !           184:             splice(@intervals, $i, 1);
        !           185:             $i--;
        !           186:         }
        !           187:     }
        !           188:     
        !           189:     # unite intervals that are not disjoint
        !           190:     # (at this point we already know that units are the same, and there is no empty interval)
        !           191:     for (my $i=0; $i < scalar(@intervals); $i++) {
        !           192:         my $inter1 = $intervals[$i];
        !           193:         for (my $j=$i+1; $j < scalar(@intervals); $j++) {
        !           194:             my $inter2 = $intervals[$j];
        !           195:             if ($inter1->qmax->value < $inter2->qmin->value || $inter1->qmin->value > $inter2->qmax->value) {
        !           196:                 next;
        !           197:             }
        !           198:             if ($inter1->qmax->equals($inter2->qmin) && $inter1->qmaxopen && $inter2->qminopen) {
        !           199:                 next;
        !           200:             }
        !           201:             if ($inter1->qmin->equals($inter2->qmax) && $inter1->qmaxopen && $inter2->qminopen) {
        !           202:                 next;
        !           203:             }
        !           204:             $intervals[$i] = $inter1->union($inter2);
        !           205:             splice(@intervals, $j, 1);
        !           206:             $i--;
        !           207:             last;
        !           208:         }
        !           209:     }
        !           210:     
        !           211:     # sort the intervals
        !           212:     for (my $i=0; $i < scalar(@intervals); $i++) {
        !           213:         my $inter1 = $intervals[$i];
        !           214:         for (my $j=$i+1; $j < scalar(@intervals); $j++) {
        !           215:             my $inter2 = $intervals[$j];
        !           216:             if ($inter1->qmin > $inter2->qmin) {
        !           217:                 $intervals[$i] = $inter2;
        !           218:                 $intervals[$j] = $inter1;
        !           219:                 $inter1 = $intervals[$i];
        !           220:                 $inter2 = $intervals[$j];
        !           221:             }
        !           222:         }
        !           223:     }
        !           224:     
        !           225:     $self->{_intervals} = \@intervals;
        !           226: }
        !           227: 
        !           228: ##
        !           229: # Tests if this union of intervals contains a quantity.
        !           230: # @param {Quantity} q
        !           231: # @returns {boolean}
        !           232: ##
        !           233: sub contains {
        !           234:     my ( $self, $q ) = @_;
        !           235:     if (!$q->isa(Quantity)) {
        !           236:         die CalcException->new("Second member of an interval is not a quantity.");
        !           237:     }
        !           238:     foreach my $inter (@{$self->intervals}) {
        !           239:         if ($inter->contains($q)) {
        !           240:             return 1;
        !           241:         }
        !           242:     }
        !           243:     return 0;
        !           244: }
        !           245: 
        !           246: ##
        !           247: # Multiplication by a Quantity
        !           248: # @param {Quantity} q
        !           249: # @returns {QIntervalUnion}
        !           250: ##
        !           251: sub qmult {
        !           252:     my ( $self, $q ) = @_;
        !           253:     if (!$q->isa(Quantity)) {
        !           254:         die CalcException->new("Intervals can only be multiplied by quantities.");
        !           255:     }
        !           256:     my @t = ();
        !           257:     foreach my $inter (@{$self->intervals}) {
        !           258:         push(@t, $inter * $q);
        !           259:     }
        !           260:     return QIntervalUnion->new(\@t);
        !           261: }
        !           262: 
        !           263: ##
        !           264: # Union
        !           265: # @param {QIntervalUnion|QInterval} qui
        !           266: # @returns {QIntervalUnion|QInterval}
        !           267: ##
        !           268: sub union {
        !           269:     my ( $self, $qiu ) = @_;
        !           270:     if (!$qiu->isa(QIntervalUnion) && !$qiu->isa(QInterval)) {
        !           271:         die CalcException->new("Cannot form a union if second  member is not an interval union or an interval.");
        !           272:     }
        !           273:     my @t = ();
        !           274:     foreach my $inter (@{$self->intervals}) {
        !           275:         push(@t, $inter->clone());
        !           276:     }
        !           277:     if ($qiu->isa(QInterval)) {
        !           278:         push(@t, $qiu->clone());
        !           279:     } else {
        !           280:         foreach my $inter (@{$qiu->intervals}) {
        !           281:             push(@t, $inter->clone());
        !           282:         }
        !           283:     }
        !           284:     my $new_union = QIntervalUnion->new(\@t); # will be reduced in the constructor
        !           285:     if (scalar(@{$new_union->intervals}) == 1) {
        !           286:         return $new_union->intervals->[0];
        !           287:     }
        !           288:     return $new_union;
        !           289: }
        !           290: 
        !           291: ##
        !           292: # Intersection
        !           293: # @param {QIntervalUnion|QInterval} qui
        !           294: # @returns {QIntervalUnion|QInterval}
        !           295: ##
        !           296: sub intersection {
        !           297:     my ( $self, $qiu ) = @_;
        !           298:     if (!$qiu->isa(QIntervalUnion) && !$qiu->isa(QInterval)) {
        !           299:         die CalcException->new("Cannot form an intersection if second member is not an interval union or an interval.");
        !           300:     }
        !           301:     my @t = ();
        !           302:     my $intervals2;
        !           303:     if ($qiu->isa(QInterval)) {
        !           304:         $intervals2 = [$qiu];
        !           305:     } else {
        !           306:         $intervals2 = $qiu->intervals;
        !           307:     }
        !           308:     foreach my $inter1 (@{$self->intervals}) {
        !           309:         foreach my $inter2 (@{$intervals2}) {
        !           310:             my $intersection = $inter1->intersection($inter2);
        !           311:             if (!$intersection->is_empty()) {
        !           312:                 push(@t, $intersection);
        !           313:             }
        !           314:         }
        !           315:     }
        !           316:     my $new_qiu = QIntervalUnion->new(\@t);
        !           317:     if (scalar(@{$new_qiu->intervals}) == 1) {
        !           318:         return $new_qiu->intervals->[0];
        !           319:     }
        !           320:     return $new_qiu;
        !           321: }
        !           322: 
        !           323: ##
        !           324: # Equals
        !           325: # @param {Quantity|QVector|QMatrix|QSet|QInterval} qui
        !           326: # @optional {string|float} tolerance
        !           327: # @returns {Quantity}
        !           328: ##
        !           329: sub qeq {
        !           330:     my ( $self, $qui, $tolerance ) = @_;
        !           331:     my $q = $self->equals($qui, $tolerance);
        !           332:     return Quantity->new($q);
        !           333: }
        !           334: 
        !           335: 
        !           336: 1;
        !           337: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>