Parent Directory
|
Revision Log
updates from Davide
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: pg/macros/contextInequalities.pl,v 1.23 2010/03/22 11:01:55 dpvc Exp $ 5 # 6 # This program is free software; you can redistribute it and/or modify it under 7 # the terms of either: (a) the GNU General Public License as published by the 8 # Free Software Foundation; either version 2, or (at your option) any later 9 # version, or (b) the "Artistic License" which comes with this package. 10 # 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 14 # Artistic License for more details. 15 ################################################################################ 16 17 18 =head1 NAME 19 20 Context("Inequalities"), Context("Inequalities-Only") - Provides contexts that 21 allow intervals to be specified as inequalities. 22 23 =head1 DESCRIPTION 24 25 Implements contexts that provides for inequalities that produce 26 the cooresponding Interval, Set or Union MathObjects. There are 27 two such contexts: Context("Inequalities"), in which both 28 intervals and inequalities are defined, and Context("Inequalities-Only"), 29 which allows only inequalities as a means of producing intervals. 30 31 =head1 USAGE 32 33 loadMacros("contextInequalities.pl"); 34 35 Context("Inequalities"); 36 $S1 = Compute("1 < x <= 4"); 37 $S2 = Inequality("(1,4]"); # force interval to be inequality 38 39 Context("Inequalities-Only"); 40 $S1 = Compute("1 < x <= 4"); 41 $S2 = Inequality("(1,4]"); # generates an error 42 43 $S3 = Compute("x < -2 or x > 2"); # forms the Union (-inf,-2) U (2,inf) 44 $S4 = Compute("x > 2 and x <= 4"); # forms the Interval (2,4] 45 $S5 = Compute("x = 1"); # forms the Set 46 $S6 = Compute("x != 1"); # forms the Union (-inf,1) U (1,inf) 47 48 You can set the "noneWord" flag to specify the string to 49 use when the inequalities specify the empty set. By default, 50 it is "NONE", but you can change it to other strings. Be sure 51 that you use a string that is defined in the Context, however, 52 if you expect the student to be able to enter it. For example 53 54 Context("Inequalities"); 55 Context()->constants->add(EmptySet => Set()); 56 Context()->flags->set(noneWord=>"EmptySet"); 57 58 creates an empty set as a named constant and uses that name. 59 60 In addition to the noneWord flag, the inequality contexts accept the 61 following additional flags: 62 63 =over 64 65 =item S<C<< showNotEquals >>> 66 67 This controls whether intervals of the form (-inf,a) U (a,inf) are 68 displayed as x != a or not. The default is 1, meaning convert to 69 x != a. 70 71 =item S<C<< allowSloppyInequalities >>> 72 73 This controls whether <= and >= can also be represented by =< and => 74 or not. By default, both forms are allowed, to allow maximum 75 flexibility in student answers, but if set to 0, only the first forms 76 are allowed. 77 78 =back 79 80 Inequalities and interval notation both can coexist side by 81 side, but you may wish to convert from one to the other. 82 Use Inequality() to convert from an Interval, Set or Union 83 to an Inequality, and use Interval(), Set(), or Union() to 84 convert from an Inequality object to one in interval notation. 85 For example: 86 87 $I0 = Compute("(1,2]"); # the interval (1,2] 88 $I1 = Inequality($I1); # the inequality 1 < x <= 2 89 90 $I0 = Compute("1 < x <= 2"); # the inequality 1 < x <= 2 91 $I1 = Interval($I0); # the interval (1,2] 92 93 Note that ineqaulities and inervals can be compared and combined 94 regardless of the format, so $I0 == $I1 is true in either example 95 above. 96 97 Since Inequality objects are actually Interval objects, the variable 98 used to create them doesn't matter. That is, 99 100 $I0 = Compute("1 < x <= 2"); 101 $I1 = Compute("1 < y <= 2"); 102 103 would both produce the same interval, so $I0 == $I1 would be true in 104 this case. If you need to distinguish between these two, use 105 106 $I0 == $I1 && $I0->{varName} eq $I1->{varName} 107 108 instead. 109 110 =cut 111 112 loadMacros("MathObjects.pl"); 113 114 sub _contextInequalities_init {Inequalities::Init()} 115 116 package Inequalities; 117 118 # 119 # Sets up the two inequality contexts 120 # 121 sub Init { 122 my $context = $main::context{Inequalities} = Parser::Context->getCopy("Interval"); 123 $context->{name} = "Inequalities"; 124 $context->operators->add( 125 '<' => {precedence => .5, associativity => 'left', type => 'bin', string => ' < ', 126 class => 'Inequalities::BOP::inequality', eval => 'evalLessThan', combine => 1}, 127 128 '>' => {precedence => .5, associativity => 'left', type => 'bin', string => ' > ', 129 class => 'Inequalities::BOP::inequality', eval => 'evalGreaterThan', combine => 1}, 130 131 '<=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' <= ', TeX => '\le ', 132 class => 'Inequalities::BOP::inequality', eval => 'evalLessThanOrEqualTo', combine => 1}, 133 '=<' => {precedence => .5, associativity => 'left', type => 'bin', string => ' <= ', TeX => '\le ', 134 class => 'Inequalities::BOP::inequality', eval => 'evalLessThanOrEqualTo', combine => 1, 135 isSloppy => "<="}, 136 137 '>=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' >= ', TeX => '\ge ', 138 class => 'Inequalities::BOP::inequality', eval => 'evalGreaterThanOrEqualTo', combine => 1}, 139 '=>' => {precedence => .5, associativity => 'left', type => 'bin', string => ' >= ', TeX => '\ge ', 140 class => 'Inequalities::BOP::inequality', eval => 'evalGreaterThanOrEqualTo', combine => 1, 141 isSloppy => ">="}, 142 143 '=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' = ', 144 class => 'Inequalities::BOP::inequality', eval => 'evalEqualTo'}, 145 146 '!=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' != ', TeX => '\ne ', 147 class => 'Inequalities::BOP::inequality', eval => 'evalNotEqualTo'}, 148 149 'and' => {precedence => .45, associateivity => 'left', type => 'bin', string => " and ", 150 TeX => '\hbox{ and }', class => 'Inequalities::BOP::and'}, 151 152 'or' => {precedence => .4, associateivity => 'left', type => 'bin', string => " or ", 153 TeX => '\hbox{ or }', class => 'Inequalities::BOP::or'}, 154 ); 155 $context->operators->set( 156 '+' => {class => "Inequalities::BOP::add"}, 157 '-' => {class => "Inequalities::BOP::subtract"}, 158 ); 159 $context->parens->set("(" => {type => "List", formInterval => ']'}); # trap these later 160 $context->parens->set("[" => {type => "List", formInterval => ')'}); # trap these later 161 $context->strings->remove("NONE"); 162 $context->constants->add(NONE=>Value::Set->new()); 163 $context->flags->set( 164 noneWord => 'NONE', 165 showNotEquals => 1, # display (-inf,a) U (a,inf) as x != a 166 allowSloppyInequalities => 1, # allow =< and => as equivalent to <= and >= 167 ); 168 $context->{parser}{Variable} = "Inequalities::Variable"; 169 $context->{value}{'Interval()'} = "Inequalities::MakeInterval"; 170 $context->{value}{Inequality} = "Inequalities::Inequality"; 171 $context->{value}{InequalityInterval} = "Inequalities::Interval"; 172 $context->{value}{InequalityUnion} = "Inequalities::Union"; 173 $context->{value}{InequalitySet} = "Inequalities::Set"; 174 $context->{value}{List} = "Inequalities::List"; 175 $context->{precedence}{Inequality} = $context->{precedence}{special}; 176 $context->lists->set(List => {class => 'Inequalities::List::List'}); 177 178 # 179 # Disable interval notation in "Inequalities-Only" context 180 # 181 $context = $main::context{"Inequalities-Only"} = $context->copy; 182 $context->lists->set( 183 Interval => {class => 'Inequalities::List::notAllowed'}, 184 Set => {class => 'Inequalities::List::notAllowed'}, 185 Union => {class => 'Inequalities::List::notAllowed'}, 186 ); 187 $context->operators->set('U' => {class => 'Inequalities::BOP::union'}); 188 $context->constants->remove('R'); 189 190 # 191 # Define the Inequality() constructor 192 # 193 main::PG_restricted_eval('sub Inequality {Value->Package("Inequality")->new(@_)}'); 194 } 195 196 197 ################################################## 198 # 199 # General BOP that handles the inequalities. 200 # The difference comes in the _eval() method, 201 # which tells what each computes. 202 # 203 package Inequalities::BOP::inequality; 204 our @ISA = ("Parser::BOP"); 205 206 # 207 # Check that the inequality is formed between a variable and a number, 208 # or between a number and another compatible inequality. Otherwise, 209 # give an error. 210 # 211 # varPos and numPos tell which of lop or rop is the variable and which 212 # the number. varName is the variable involved in the inequality. 213 # 214 sub _check { 215 my $self = shift; 216 $self->Error("'%s' should be written '%s'",$self->{bop},$self->{def}{isSloppy}) 217 if (!$self->context->flag("allowSloppyInequalities") && $self->{def}{isSloppy}); 218 $self->{type} = $Value::Type{interval}; 219 $self->{isInequality} = 1; 220 ($self->{varPos},$self->{numPos}) = 221 ($self->{lop}->class eq 'Variable' || $self->{lop}{isInequality} ? ('lop','rop') : ('rop','lop')); 222 my ($v,$n) = ($self->{$self->{varPos}},$self->{$self->{numPos}}); 223 if (($n->isNumber || $n->{isInfinite}) && ($n->{isConstant} || scalar(keys %{$n->getVariables}) == 0)) { 224 if ($v->class eq 'Variable') { 225 $self->{varName} = $v->{name}; 226 delete $self->{equation}{variables}{$v->{name}} if $v->{isNew}; 227 $self->{$self->{varPos}} = Inequalities::DummyVariable->new($self->{equation},$v->{name},$v->{ref}); 228 return; 229 } 230 if ($self->{def}{combine} && $v->{isInequality}) { 231 my $bop = $self->{bop}; $bop =~ s/=//; my $bope = $bop."="; my $ebop = "=".$bop; 232 if (($v->{bop} eq $bop || $v->{bop} eq $bope || $v->{bop} eq $ebop) && $v->{varPos} eq $self->{numPos}) { 233 $self->{varName} = $v->{varName}; 234 return; 235 } 236 } 237 } 238 $self->Error("'%s' should have a variable on one side and a number on the other",$self->{bop}) 239 unless $v->{isInequality} && $v->{varPos} eq $self->{numPos}; 240 $self->Error("'%s' can't be combined with '%s'",$v->{bop},$self->{bop}); 241 } 242 243 # 244 # Generate the interval for the given type of inequality. 245 # If it is a combined inequality, intersect with the other 246 # one to get the final set. 247 # 248 sub _eval { 249 my $self = shift; my ($a,$b) = @_; 250 my $eval = $self->{def}{eval}; 251 my $I = $self->Package("Inequality")->new($self->context,$self->$eval(@_),$self->{varName}); 252 return $I->intersect($a) if Value::isValue($a) && $a->type eq 'Interval'; 253 return $I->intersect($b) if Value::isValue($b) && $b->type eq 'Interval'; 254 return $I; 255 } 256 257 sub evalLessThan { 258 my ($self,$a,$b) = @_; my $context = $self->context; 259 my $I = Value::Infinity->new($context); 260 return $self->Package("Interval")->new($context,'(',-$I,$b,')') if $self->{varPos} eq 'lop'; 261 return $self->Package("Interval")->new($context,'(',$a,$I,')'); 262 } 263 264 sub evalGreaterThan { 265 my ($self,$a,$b) = @_; my $context = $self->context; 266 my $I = Value::Infinity->new; 267 return $self->Package("Interval")->new($context,'(',$b,$I,')')->with(reversed=>1) if $self->{varPos} eq 'lop'; 268 return $self->Package("Interval")->new($context,'(',-$I,$a,')')->with(reversed=>1); 269 } 270 271 sub evalLessThanOrEqualTo { 272 my ($self,$a,$b) = @_; my $context = $self->context; 273 my $I = Value::Infinity->new; 274 return $self->Package("Interval")->new($context,'(',-$I,$b,']') if $self->{varPos} eq 'lop'; 275 return $self->Package("Interval")->new($context,'[',$a,$I,')'); 276 } 277 278 sub evalGreaterThanOrEqualTo { 279 my ($self,$a,$b) = @_; my $context = $self->context; 280 my $I = Value::Infinity->new; 281 return $self->Package("Interval")->new($context,'[',$b,$I,')')->with(reversed=>1) if $self->{varPos} eq 'lop'; 282 return $self->Package("Interval")->new($context,'(',-$I,$a,']')->with(reversed=>1); 283 } 284 285 sub evalEqualTo { 286 my ($self,$a,$b) = @_; my $context = $self->context; 287 my $x = ($self->{varPos} eq 'lop' ? $b : $a); 288 return $self->Package("Set")->new($context,$x); 289 } 290 291 sub evalNotEqualTo { 292 my ($self,$a,$b) = @_; my $context = $self->context; 293 my $x = ($self->{varPos} eq 'lop' ? $b : $a); 294 my $I = Value::Infinity->new; 295 return $self->Package("Union")->new($context, 296 $self->Package("Interval")->new($context,'(',-$I,$x,')'), 297 $self->Package("Interval")->new($context,'(',$x,$I,')') 298 )->with(notEqual=>1); 299 } 300 301 # 302 # Inequalities have dummy variables that are not really 303 # variables of a formula. 304 305 sub getVariables {{}} 306 307 # 308 # Avoid unwanted parentheses from the standard routines. 309 # 310 sub string { 311 my ($self,$precedence) = @_; 312 my $string; my $bop = $self->{def}; 313 314 $string = $self->{lop}->string($bop->{precedence}). 315 $bop->{string}. 316 $self->{rop}->string($bop->{precedence}); 317 318 return $string; 319 } 320 321 sub TeX { 322 my ($self,$precedence) = @_; 323 my $TeX; my $bop = $self->{def}; 324 325 $TeX = $self->{lop}->TeX($bop->{precedence}). 326 (defined($bop->{TeX}) ? $bop->{TeX} : $bop->{string}) . 327 $self->{rop}->TeX($bop->{precedence}); 328 329 return $TeX; 330 } 331 332 ################################################## 333 # 334 # Implements the "and" operation as set intersection 335 # 336 package Inequalities::BOP::and; 337 our @ISA = ("Parser::BOP"); 338 339 sub _check { 340 my $self = shift; 341 $self->Error("The operands of '%s' must be inequalities",$self->{bop}) 342 unless $self->{lop}{isInequality} && $self->{rop}{isInequality}; 343 $self->Error("Inequalities combined by '%s' must both use the same variable",$self->{bop}) 344 unless $self->{lop}{varName} eq $self->{rop}{varName}; 345 $self->{type} = Value::Type("Interval",2); 346 $self->{varName} = $self->{lop}{varName}; 347 $self->{isInequality} = 1; 348 } 349 350 sub _eval {$_[1]->intersect($_[2])} 351 352 ################################################## 353 # 354 # Implements the "or" operation as set union 355 # 356 package Inequalities::BOP::or; 357 our @ISA = ("Parser::BOP"); 358 359 sub _check { 360 my $self = shift; 361 $self->Error("The operands of '%s' must be inequalities",$self->{bop}) 362 unless $self->{lop}{isInequality} && $self->{rop}{isInequality}; 363 $self->Error("Inequalities combined by '%s' must both use the same variable",$self->{bop}) 364 unless $self->{lop}{varName} eq $self->{rop}{varName}; 365 $self->{type} = Value::Type("Interval",2); 366 $self->{varName} = $self->{lop}{varName}; 367 $self->{isInequality} = 1; 368 } 369 370 sub _eval {$_[1] + $_[2]} 371 372 ################################################## 373 # 374 # Subclass of Parser::Variable that records whether 375 # this variable has already been seen in the formula 376 # (so that it can be removed from the formula's 377 # variable list when used in an inequality.) 378 # 379 package Inequalities::Variable; 380 our @ISA = ("Parser::Variable"); 381 382 sub new { 383 my $self = shift; my $equation = shift; my $name = shift; 384 my $isNew = !defined $equation->{variables}{$name}; 385 my $n = $self->SUPER::new($equation,$name,@_); 386 $n->{isNew} = $isNew; 387 return $n; 388 } 389 390 ################################################## 391 # 392 # A special class used for the variables in 393 # inequalities, since they are not really 394 # variables for the formula. (They don't need 395 # to be substituted or given values when the 396 # formula is evaluated, and so on.) These are 397 # really just placeholders, here. 398 # 399 package Inequalities::DummyVariable; 400 our @ISA = ("Parser::Item"); 401 402 sub new { 403 my $self = shift; my $class = ref($self) || $self; 404 my ($equation,$name,$ref) = @_; 405 my $def = $equation->{context}{variables}{$name}; 406 bless {name => $name, ref => $ref, def => $def, equation => $equation}, $class; 407 } 408 409 sub eval {shift}; 410 411 sub string {(shift)->{name}} 412 413 sub TeX { 414 my $self = shift; my $name = $self->{name}; 415 return $self->{def}{TeX} if defined $self->{def}{TeX}; 416 $name = $1.'_{'.$2.'}' if ($name =~ m/^([^_]+)_?(\d+)$/); 417 return $name; 418 } 419 420 sub perl { 421 my $self = shift; 422 return $self->{def}{perl} if defined $self->{def}{perl}; 423 return '$'.$self->{name}; 424 } 425 426 ################################################## 427 # 428 # Give an error when U is used. 429 # 430 package Inequalities::BOP::union; 431 our @ISA = ("Parser::BOP::union"); 432 433 sub _check { 434 my $self = shift; 435 $self->Error("You can't take unions of inequalities") 436 if $self->{lop}{isInequality} || $self->{rop}{isInequality}; 437 $self->SUPER::_check(@_); 438 $self->Error("Unions are not allowed in this context"); 439 } 440 441 ################################################## 442 # 443 # Don't allow sums and differences of inequalities 444 # 445 package Inequalities::BOP::add; 446 our @ISA = ("Parser::BOP::add"); 447 448 sub _check { 449 my $self = shift; 450 $self->SUPER::_check(@_); 451 $self->Error("Can't add inequalities (do you mean to use 'or'?)") 452 if $self->{lop}{isInequality} || $self->{rop}{isInequality}; 453 } 454 455 ################################################## 456 # 457 # Don't allow sums and differences of inequalities 458 # 459 package Inequalities::BOP::subtract; 460 our @ISA = ("Parser::BOP::subtract"); 461 462 sub _check { 463 my $self = shift; 464 $self->SUPER::_check(@_); 465 $self->Error("Can't subtract inequalities") 466 if $self->{lop}{isInequality} || $self->{rop}{isInequality}; 467 } 468 469 ################################################## 470 # 471 # For the Inequalities-Only context, report 472 # an error for Intervals, Sets or Union notation. 473 # 474 package Inequalities::List::notAllowed; 475 our @ISA = ("Parser::List::List"); 476 477 sub _check {(shift)->Error("You are not allowed to use intervals or sets in this context")} 478 479 480 ################################################## 481 ################################################## 482 # 483 # Subclasses of the Interval, Set, and Union classes 484 # that stringify as inequalities 485 # 486 487 # 488 # Some common routines to all three classes 489 # 490 package Inequalities::common; 491 492 # 493 # Turn the object back into its usual Value version 494 # 495 sub demote { 496 my $self = shift; my $context = $self->context; 497 my $other = shift; $other = $self unless defined $other; 498 return $other unless Value::classMatch($other,"Inequality"); 499 $context->Package($other->type)->make($context,$other->makeData); 500 } 501 502 # 503 # Needed to get Interval data in the right order for make(), 504 # and demote all the items in a Union 505 # 506 sub makeData {(shift)->value} 507 508 # 509 # Recursively mark Intervals and Sets in a Union as Inequalities 510 # 511 sub updateParts {} 512 513 # 514 # Demote the operands to normal Value objects and 515 # perform the action, then remake the result into 516 # an Inequality again. 517 # 518 sub apply { 519 my $self = shift; my $context = $self->context; 520 my $method = shift; my $other = shift; 521 $context->Package("Inequality")->new($context, 522 $self->demote->$method($self->demote($other),@_), 523 $self->{varName}); 524 } 525 526 sub add {(shift)->apply("add",@_)} 527 sub sub {(shift)->apply("sub",@_)} 528 sub reduce {(shift)->apply("reduce",@_)} 529 sub intersect {(shift)->apply("intersect",@_)} 530 531 # 532 # The name to use for error messages in answer checkers 533 # 534 sub class {"Inequality"} 535 sub cmp_class {"an Inequality"} 536 sub showClass {"an Inequality"} 537 sub typeRef { 538 my $self = shift; 539 return Value::Type($self->type, $self->length, $Value::Type{number}); 540 } 541 542 # 543 # Get the precedence based on the type rather than the class. 544 # 545 sub precedence { 546 my $self = shift; my $precedence = $self->context->{precedence}; 547 return $precedence->{$self->type}-$precedence->{Interval}+$precedence->{$self->class}; 548 } 549 550 # 551 # Produce better error messages for inequalities 552 # 553 sub cmp_checkUnionReduce { 554 my $self = shift; my $student = shift; my $ans = shift; my $nth = shift || ''; 555 if (Value::classMatch($student,"Inequality")) { 556 return unless $ans->{studentsMustReduceUnions} && 557 $ans->{showUnionReduceWarnings} && 558 !$ans->{isPreview} && !Value::isFormula($student); 559 my ($result,$error) = $student->isReduced; 560 return unless $error; 561 return { 562 "overlaps" => "Your$nth answer contains overlapping inequalities", 563 "overlaps in sets" => "Your$nth answer contains equalities that are already included elsewhere", 564 "uncombined intervals" => "Your$nth answer can be simplified by combining some inequalities", 565 "uncombined sets" => "", # shouldn't get this from inequalities 566 "repeated elements in set" => "Your$nth answer contains repeated values", 567 "repeated elements" => "Your$nth answer contains repeated values", 568 }->{$error}; 569 } else { 570 return unless Value::can($student,"isReduced"); 571 return Value::cmp_checkUnionReduce($self,$student,$ans,$nth,@_) 572 } 573 } 574 575 576 ################################################## 577 578 package Inequalities::Interval; 579 our @ISA = ("Inequalities::common", "Value::Interval"); 580 581 sub type {"Interval"} 582 583 sub updateParts { 584 my $self = shift; 585 $self->{leftInfinite} = 1 if $self->{data}[0]->{isInfinite}; 586 $self->{rightInfinite} = 1 if $self->{data}[1]->{isInfinite}; 587 } 588 589 sub string { 590 my $self = shift; 591 my ($a,$b,$open,$close) = $self->value; 592 my $x = $self->{varName} || ($self->context->variables->names)[0]; 593 $x = $context->{variables}{$x}{string} if defined $context->{variables}{$x}{string}; 594 if ($self->{leftInfinite}) { 595 return "-infinity < $x < infinity" if $self->{rightInfinite}; 596 return $b->string . ($close eq ')' ? ' > ' : ' >= ') . $x if $self->{reversed}; 597 return $x . ($close eq ')' ? ' < ' : ' <= ') . $b->string; 598 } elsif ($self->{rightInfinite}) { 599 return $x . ($open eq '(' ? ' > ' : ' >= ') . $a->string if $self->{reversed}; 600 return $a->string . ($open eq '(' ? ' < ' : ' <= ') . $x; 601 } else { 602 return $a->string . ($open eq '(' ? ' < ' : ' <= ') . 603 $x . ($close eq ')' ? ' < ' : ' <= ') . $b->string; 604 } 605 } 606 607 sub TeX { 608 my $self = shift; 609 my ($a,$b,$open,$close) = $self->value; 610 my $context = $self->context; 611 my $x = $self->{varName} || ($context->variables->names)[0]; 612 $x = $context->{variables}{$x}{TeX} if defined $context->{variables}{$x}{TeX}; 613 $x =~ s/^([^_]+)_?(\d+)$/$1_{$2}/; 614 if ($self->{leftInfinite}) { 615 return "-\\infty < $x < \\infty" if $self->{rightInfinite}; 616 return $b->TeX . ($close eq ')' ? ' > ' : ' \ge ') . $x if $self->{reversed}; 617 return $x . ($close eq ')' ? ' < ' : ' \le ') . $b->TeX; 618 } elsif ($self->{rightInfinite}) { 619 return $x . ($open eq '(' ? ' > ' : ' \ge ') . $a->TeX if $self->{reversed}; 620 return $a->TeX . ($open eq '(' ? ' < ' : ' \le ') . $x; 621 } else { 622 return $a->TeX . ($open eq '(' ? ' < ' : ' \le ') . 623 $x . ($close eq ')' ? ' < ' : ' \le ') . $b->TeX; 624 } 625 } 626 627 ################################################## 628 629 package Inequalities::Union; 630 our @ISA = ("Inequalities::common", "Value::Union"); 631 632 sub type {"Union"} 633 634 # 635 # Mark all the parts of the union as inequalities 636 # 637 sub updateParts { 638 my $self = shift; 639 foreach my $I (@{$self->{data}}) { 640 $I->{varName} = $self->{varName}; 641 $I->{reduceSets} = $I->{"is".$I->type} = 1; 642 bless $I, $self->Package("Inequality".$I->type); 643 $I->updateParts; 644 } 645 } 646 647 # 648 # Update the intervals and sets when a new union is made 649 # 650 sub make { 651 my $self = (shift)->SUPER::make(@_); 652 $self->updateParts; 653 return $self; 654 } 655 656 # 657 # Demote all the items in the union 658 # 659 sub makeData { 660 my $self = shift; my @U = (); 661 foreach my $I (@{$self->{data}}) {push(@U,$I->demote)} 662 return @U; 663 } 664 665 sub string { 666 my $self = shift; 667 my $equation = shift; shift; shift; my $prec = shift; 668 return $self->display("string",$equation,$prec); 669 } 670 671 sub TeX { 672 my $self = shift; 673 my $equation = shift; shift; shift; my $prec = shift; 674 return $self->display("TeX",$equation,$prec); 675 } 676 677 sub display { 678 my $self = shift; my $method = shift; my $equation = shift; my $prec = shift; 679 my $context = ($equation->{context} || $self->context); 680 my $X = $self->{varName} || ($context->variables->names)[0]; 681 $X = $context->{variables}{$X}{$method} if defined $context->{variables}{$X}{$method}; 682 $X =~ s/^([^_]+)_?(\d+)$/$1_{$2}/ if $method eq 'TeX'; 683 my $op = $context->{operators}{'or'}; 684 my ($and,$or,$le,$ge,$ne,$open,$close) = @{{ 685 string => [' and ',$op->{string} || ' or ',' <= ',' >= ',' != ','(',')'], 686 TeX => ['\hbox{ and }',$op->{TeX} || $op->{string} || '\hbox{ or }', 687 ' \le ',' \ge ',' \ne ','\left(','\right)'], 688 }->{$method}}; 689 my $showNE = $self->getFlag("showNotEquals",1); 690 my @intervals = (); my @points = (); my $interval; 691 foreach my $x (@{$self->data}) { 692 $x->{format} = $self->{format} if defined $self->{format}; 693 if ($x->type eq 'Interval' && $showNE) { 694 if (defined($interval)) { 695 if ($interval->{data}[1] == $x->{data}[0]) { 696 push(@points,$X.$ne.$x->{data}[0]->$method($equation)); 697 $interval = $interval->with(isCopy=>1, data=>[$interval->value]) unless $interval->{isCopy}; 698 $interval->{data}[1] = $x->{data}[1]; 699 $interval->{rightInfinite} = 1 if $x->{rightInfinite}; 700 next; 701 } 702 push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)); 703 } 704 $interval = $x; @points = (); next; 705 } 706 if (defined($interval)) { 707 push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)); 708 $interval = undef; @points = (); 709 } 710 push(@intervals,$x->$method($equation)); 711 } 712 push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)) if defined($interval); 713 my $string = join($or,@intervals); 714 $string = $open.$string.$close if defined($prec) && $prec > ($op->{precedence} || 1.5); 715 return $string; 716 } 717 718 sub joinAnd { 719 my $self = shift; $interval = shift; $method = shift, my $and = shift; my $equation = shift; 720 unshift(@_,$interval->$method($equation)) unless $interval->{leftInfinite} && $interval->{rightInfinite}; 721 return join($and, @_); 722 } 723 724 ################################################## 725 726 package Inequalities::Set; 727 our @ISA = ("Inequalities::common", "Value::Set"); 728 729 sub type {"Set"} 730 731 sub string { 732 my $self = shift; my $equation = shift; 733 my $x = $self->{varName} || ($self->context->variables->names)[0]; 734 $x = $context->{variables}{$x}{string} if defined $context->{variables}{$x}{string}; 735 my @coords = (); 736 foreach my $a (@{$self->data}) { 737 if (Value::isValue($a)) { 738 $a->{format} = $self->{format} if defined $self->{format}; 739 push(@coords,$x.' = '.$a->string($equation)); 740 } else { 741 push(@coords,$x.' = '.$a); 742 } 743 } 744 return $self->getFlag('noneWord') unless scalar(@coords); 745 return join(" or ",@coords); 746 } 747 748 sub TeX { 749 my $self = shift; my $equation = shift; 750 my $x = $self->{varName} || ($self->context->variables->names)[0]; 751 $x = $context->{variables}{$x}{TeX} if defined $context->{variables}{$x}{TeX}; 752 $x =~ s/^([^_]+)_?(\d+)$/$1_{$2}/; 753 my @coords = (); 754 foreach my $a (@{$self->data}) { 755 if (Value::isValue($a)) { 756 $a->{format} = $self->{format} if defined $self->{format}; 757 push(@coords,$x.' = '.$a->TeX($equation)); 758 } else { 759 push(@coords,$x.' = '.$a); 760 } 761 } 762 return '\hbox{'.$self->getFlag('noneWord').'}' unless scalar(@coords); 763 return join('\hbox{ or }',@coords); 764 } 765 766 ################################################## 767 # 768 # A class for making inequalities by hand 769 # 770 package Inequalities::Inequality; 771 our @ISA = ('Value'); 772 773 sub new { 774 my $self = shift; my $class = ref($self) || $self; 775 my $context = (Value::isContext($_[0]) ? shift : $self->context); 776 my $S = shift; my $x = shift; 777 $S = Value::makeValue($S,context=>$context); 778 if (Value::classMatch($S,"Inequality")) { 779 if (defined($x)) {$S->{varName} = $x; $S->updateParts} 780 return $S; 781 } 782 $x = ($context->variables->names)[0] unless $x; 783 $S = bless $S->inContext($context), $context->Package("Inequality".$S->type); 784 $S->{varName} = $x; $S->{reduceSets} = $S->{"is".$S->Type} = 1; 785 $S->updateParts; 786 return $S; 787 } 788 789 ################################################## 790 # 791 # Allow Interval() to coerce types to Value::Interval 792 # 793 package Inequalities::MakeInterval; 794 our @ISA = ("Value::Interval"); 795 796 sub new { 797 my $self = shift; 798 $self = $self->SUPER::new(@_); 799 $self = $self->demote if $self->classMatch("Inequality"); 800 return $self; 801 } 802 803 ################################################## 804 # 805 # Mark this as a list of inequalities (if it is) 806 # 807 package Inequalities::List; 808 our @ISA = ("Value::List"); 809 810 sub new { 811 my $self = (shift)->SUPER::new(@_); 812 return $self unless $self->{type} =~ m/^(unknown|Interval|Set|Union)$/; 813 foreach my $x (@{$self->{data}}) {return $self unless Value::classMatch($x,'Inequality')} 814 $self->{type} = 'Inequality'; 815 return $self; 816 } 817 818 package Inequalities::List::List; 819 our @ISA = ("Parser::List::List"); 820 821 sub _check { 822 my $self = shift; $self->SUPER::_check(@_); 823 if ($self->canBeInUnion) { 824 # 825 # Convert lists that look like intervals into intervals 826 # and then check if they are OK. 827 # 828 bless $self, $self->context->{lists}{Interval}{class}; 829 $self->{type} = $Value::Type{interval}; 830 $self->{parens} = $self->context->{parens}{interval}; 831 $self->_check; 832 } else { 833 my $entryType = $self->typeRef->{entryType}; 834 return unless $entryType->{name} =~ m/^(unknown|Interval|Set|Union)$/; 835 foreach my $x (@{$self->{coords}}) {return unless $x->{isInequality}}; 836 $entryType->{name} = "Inequality"; 837 } 838 } 839 840 ################################################## 841 842 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |