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