[system] / trunk / pg / macros / contextInequalities.pl Repository: Repository Listing bbplugincoursesdistsnplrochestersystemwww

# View of /trunk/pg/macros/contextInequalities.pl

Wed Aug 22 23:35:41 2007 UTC (12 years, 5 months ago) by dpvc
File size: 17239 byte(s)
Set the cmpDefaults so that you don't get error messages for things
like "x=1 or x=2" (which are not reduced when considered as {1} U {2}).
Also, improve the error messages a bit.

There is a better way to handle this context so that intervals, sets
and unions are distinct from inequalities.  That will clear up the
ambiguity about how to stringify the objects, and will make it easier
for the two types of notation to coincide.


    1 loadMacros("MathObjects.pl");
2
3 sub _contextInequalities_init {Inequalities::Init()}
4
6
7  #########################################################################
8  #
9  #  Implements contexts that provides for inequalities that produce
10  #  the cooresponding Interval, Set or Union MathObjects.  There are
11  #  two such contexts:  Context("Inequalities"), in which both
12  #  intervals and inequalities are defined, and Context("Inequalities-Only"),
13  #  which allows only inequalities as a means of producing intervals.
14  #
16  #
17  #            Context("Inequalities");
18  #            $S1 = Formula("1 < x <= 4"); 19 #$S2 = Formula("(1,4]");        # either form is OK
20  #
21  #            Context("Inequalities-Only");
22  #            $S1 = Formula("1 < x <= 4"); 23 #$S2 = Formula("(1,4]");        # generates an error
24  #
25  #            $S3 = Formula("x < -2 or x > 2"); # forms a Union 26 #$S4 = Formula("x = 1");            # forms a Set
27  #
28  #  You can set the "stringifyAsInequalities" flag to 1 to force
29  #  output from the intervals, sets, and unions created in this
30  #  context to be output as inequalities rather than their
31  #  usual Inerval, Set or Union forms.
32  #
33  #     Context("Inequalities")->flags->set(stringifyAsInequalities=>1);
34  #
35  #  You can also set the "noneWord" flag to specify the string to
36  #  use when the inequalities specify the empty set.  By default,
37  #  it is "NONE", but you can change it to other strings.  Be sure
38  #  that you use a string that is defined in the Context, however,
39  #  if you expect the student to be able to enter it.  For example
40  #
41  #    Context("Inequalities");
43  #    Context()->flags->set(noneWord=>"EmptySet");
44  #
45  #  creates an empty set as a named constant and uses that name.
46  #
47
48 =cut
49
50 package Inequalities;
51
52 #
53 #  Sets up the two inequality contexts
54 #
55 sub Init {
56   my $context =$main::context{Inequalities} = Parser::Context->getCopy("Interval");
57   $context->operators->add( 58 '<' => {precedence => .5, associativity => 'left', type => 'bin', string => ' < ', 59 class => 'Inequalities::BOP::inequality', eval => 'evalLessThan', combine => 1}, 60 61 '>' => {precedence => .5, associativity => 'left', type => 'bin', string => ' > ', 62 class => 'Inequalities::BOP::inequality', eval => 'evalGreaterThan', combine => 1}, 63 64 '<=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' <= ', 65 class => 'Inequalities::BOP::inequality', eval => 'evalLessThanOrEqualTo', combine => 1}, 66 67 '>=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' >= ', 68 class => 'Inequalities::BOP::inequality', eval => 'evalGreaterThanOrEqualTo', combine => 1}, 69 70 '=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' = ', 71 class => 'Inequalities::BOP::inequality', eval => 'evalEqualTo'}, 72 73 '!=' => {precedence => .5, associativity => 'left', type => 'bin', string => ' != ', 74 class => 'Inequalities::BOP::inequality', eval => 'evalNotEqualTo'}, 75 76 'and' => {precedence => .45, associateivity => 'left', type => 'bin', string => " and ", 77 TeX => '\hbox{ and }', class => 'Inequalities::BOP::and'}, 78 79 'or' => {precedence => .4, associateivity => 'left', type => 'bin', string => " or ", 80 TeX => '\hbox{ or }', class => 'Inequalities::BOP::or'}, 81 ); 82$context->flags->set(stringifyAsInequalities => 0, noneWord => 'NONE');
83   $context->{cmpDefaults}{Inequalities} = {reduceSets=>1}; 84$context->{cmpDefaults}{Interval} = {reduceSets=>1};
85   $context->{cmpDefaults}{Union} = {reduceSets=>1}; 86$context->{cmpDefaults}{Set} = {reduceSets=>1};
87   $context->strings->remove("NONE"); 88$context->constants->add(NONE=>Value::Set->new());
89   $context->{parser}{Variable} = "Inequalities::Variable"; 90$context->{value}{Interval} = "Inequalities::Interval";
91   $context->{value}{Union} = "Inequalities::Union"; 92$context->{value}{Set} = "Inequalities::Set";
93
94   #
95   #  Disable interval notation in Context("Inequalities-Only");
96   #
97   $context =$main::context{"Inequalities-Only"} = $context->copy; 98$context->parens->remove('(','[','{');
99   $context->parens->redefine('(',from=>"Numeric"); 100$context->parens->redefine('[',from=>"Numeric");
101   $context->parens->redefine('{',from=>"Numeric"); 102$context->parens->set(
103     '(' => {formInterval=>0},
104     '[' => {formInterval=>0}
105   );
106   $context->lists->set(List => {class => 'Inequalities::List::List'}); 107$context->operators->remove('U');
108   $context->constants->remove('R'); 109 return; 110 } 111 112 113 ################################################## 114 # 115 # General BOP that handles the inequalities. 116 # The difference comes in the _eval() method, 117 # which tells what each computes. 118 # 119 package Inequalities::BOP::inequality; 120 our @ISA = ("Parser::BOP"); 121 122 # 123 # Check that the inequality is formed between a variable and a number, 124 # or between a number and another compatable inequality. Otherwise, 125 # give an error. 126 # 127 # varPos and numPos tell which of lop or rop is the variable and which 128 # the number. varName is the variable involved in the inequality. 129 # 130 sub _check { 131 my$self = shift;
132   $self->{type} = Value::Type("Interval",2); 133$self->{isInequality} = 1;
134   ($self->{varPos},$self->{numPos}) =
135     ($self->{lop}->class eq 'Variable' ||$self->{lop}{isInequality} ? ('lop','rop') : ('rop','lop'));
136   my ($v,$n) = ($self->{$self->{varPos}},$self->{$self->{numPos}});
137   if ($n->isNumber &&$n->{isConstant}) {
138     if ($v->class eq 'Variable') { 139$self->{varName} = $v->{name}; 140 delete$self->{equation}{variables}{$v->{name}} if$v->{isNew};
141       $self->{$self->{varPos}} = Inequalities::DummyVariable->new($self->{equation},$v->{name},$v->{ref}); 142 return; 143 } 144 if ($self->{def}{combine} && $v->{isInequality}) { 145 my$bop = substr($self->{bop},0,1); my$ebop = $bop."="; 146 if (($v->{bop} eq $bop ||$v->{bop} eq $ebop) &&$v->{varPos} eq $self->{numPos}) { 147$self->{varName} = $v->{varName}; 148 return; 149 } 150 } 151 } 152$self->Error("'%s' should have a variable on one side and a number on the other",$self->{bop}) 153 unless$v->{isInequality} && $v->{varPos} eq$self->{numPos};
154   $self->Error("'%s' can't be combined with '%s'",$v->{bop},$self->{bop}); 155 } 156 157 # 158 # Generate the interval for the given type of inequality. 159 # If it is a combined inequality, intersect with the other 160 # one to get the final set. 161 # 162 sub _eval { 163 my$self = shift; my ($a,$b) = @_;
164   my $eval =$self->{def}{eval};
165   my $I =$self->$eval(@_); 166 return$I->intersect($a) if Value::isValue($a) && $a->type eq 'Interval'; 167 return$I->intersect($b) if Value::isValue($b) && $b->type eq 'Interval'; 168 return$I;
169 }
170
171 sub evalLessThan {
172   my ($self,$a,$b) = @_; my$context = $self->context; 173 my$I = Value::Infinity->new($context); 174 return$self->Package("Interval")->new($context,'(',-$I,$b,')') if$self->{varPos} eq 'lop';
175   return $self->Package("Interval")->new($context,'(',$a,$I,')');
176 }
177
178 sub evalGreaterThan {
179   my ($self,$a,$b) = @_; my$context = $self->context; 180 my$I = Value::Infinity->new;
181   return $self->Package("Interval")->new($context,'(',$b,$I,')') if $self->{varPos} eq 'lop'; 182 return$self->Package("Interval")->new($context,'(',-$I,$a,')'); 183 } 184 185 sub evalLessThanOrEqualTo { 186 my ($self,$a,$b) = @_; my $context =$self->context;
187   my $I = Value::Infinity->new; 188 return$self->Package("Interval")->new($context,'(',-$I,$b,']') if$self->{varPos} eq 'lop';
189   return $self->Package("Interval")->new($context,'[',$a,$I,')');
190 }
191
192 sub evalGreaterThanOrEqualTo {
193   my ($self,$a,$b) = @_; my$context = $self->context; 194 my$I = Value::Infinity->new;
195   return $self->Package("Interval")->new($context,'[',$b,$I,')') if $self->{varPos} eq 'lop'; 196 return$self->Package("Interval")->new($context,'(',-$I,$a,']'); 197 } 198 199 sub evalEqualTo { 200 my ($self,$a,$b) = @_; my $context =$self->context;
201   my $x = ($self->{varPos} eq 'lop' ? $b :$a);
202   return $self->Package("Set")->new($context,$x); 203 } 204 205 sub evalNotEqualTo { 206 my ($self,$a,$b) = @_; my $context =$self->context;
207   my $x = ($self->{varPos} eq 'lop' ? $b :$a);
208   my $I = Value::Infinity->new; 209 return$self->Package("Union")->new($context, 210$self->Package("Interval")->new($context,'(',-$I,$x,')'), 211$self->Package("Interval")->new($context,'(',$x,$I,')') 212 ); 213 } 214 215 # 216 # Inequalities have dummy variables that are not really 217 # variables of a formula. 218 219 sub getVariables {{}} 220 221 # 222 # Avoid unwanted parentheses from the standard routines. 223 # 224 sub string { 225 my ($self,$precedence) = @_; 226 my$string; my $bop =$self->{def};
227
228   $string =$self->{lop}->string($bop->{precedence}). 229$bop->{string}.
230             $self->{rop}->string($bop->{precedence});
231
232   return $string; 233 } 234 235 sub TeX { 236 my ($self,$precedence) = @_; 237 my$TeX; my $bop =$self->{def};
238
239   $TeX =$self->{lop}->TeX($bop->{precedence}). 240 (defined($bop->{TeX}) ? $bop->{TeX} :$bop->{string}) .
241          $self->{rop}->TeX($bop->{precedence});
242
243   return $TeX; 244 } 245 246 ################################################## 247 # 248 # Implements the "and" operation as set intersection 249 # 250 package Inequalities::BOP::and; 251 our @ISA = ("Parser::BOP"); 252 253 sub _check { 254 my$self = shift;
255   $self->Error("The operands of '%s' must be Intervals, Sets or Unions") 256 unless$self->{lop}->isSetOfReals && $self->{rop}->isSetOfReals; 257$self->{type} = Value::Type("Interval",2);
258   $self->{varName} =$self->{lop}{varName} || $self->{rop}{varName}; 259 } 260 261 sub _eval {$_[1]->intersect($_[2])} 262 263 ################################################## 264 # 265 # Implements the "or" operation as set union 266 # 267 package Inequalities::BOP::or; 268 our @ISA = ("Parser::BOP"); 269 270 sub _check { 271 my$self = shift;
272   $self->Error("The operands of '%s' must be Intervals, Sets or Unions") 273 unless$self->{lop}->isSetOfReals && $self->{rop}->isSetOfReals; 274$self->{type} = Value::Type("Interval",2);
275   $self->{varName} =$self->{lop}{varName} || $self->{rop}{varName}; 276 } 277 278 sub _eval {$_[1] + $_[2]} 279 280 ################################################## 281 # 282 # Subclass of Parser::Variable that records whether 283 # this variable has already been seen in the formula 284 # (so that it can be removed from the formula's 285 # variable list when used in an inequality.) 286 # 287 package Inequalities::Variable; 288 our @ISA = ("Parser::Variable"); 289 290 sub new { 291 my$self = shift; my $equation = shift; my$name = shift;
292   my $isNew = !defined$equation->{variables}{$name}; 293 my$n = $self->SUPER::new($equation,$name,@_); 294$n->{isNew} = $isNew; 295 return$n;
296 }
297
298 ##################################################
299 #
300 #  A special class usd for the variables in
301 #  inequalities, since they are not really
302 #  variables for the formula.  (They don't need
303 #  to be subtituted or given values when the
304 #  formula is evaluated, and so on.)  These are
305 #  really just placeholders, here.
306 #
307 package Inequalities::DummyVariable;
308 our @ISA = ("Parser::Item");
309
310 sub new {
311   my $self = shift; my$class = ref($self) ||$self;
312   my ($equation,$name,$ref) = @_; 313 my$def = $equation->{context}{variables}{$name};
314   bless {name => $name, ref =>$ref, def => $def, equation =>$equation}, $class; 315 } 316 317 sub eval {shift}; 318 319 sub string {(shift)->{name}} 320 321 sub TeX { 322 my$self = shift; my $name =$self->{name};
323   return $self->{def}{TeX} if defined$self->{def}{TeX};
324   $name =$1.'_{'.$2.'}' if ($name =~ m/^([^_]+)_?(\d+)$/); 325 return$name;
326 }
327
328 sub perl {
329   my $self = shift; 330 return$self->{def}{perl} if defined $self->{def}{perl}; 331 return '$'.$self->{name}; 332 } 333 334 ################################################## 335 # 336 # For the Inequalities-Only context, we make lists 337 # that report errors, so that students MUST produce 338 # their intervals via inequalities. 339 # 340 package Inequalities::List::List; 341 our @ISA = ("Parser::List::List"); 342 343 sub _check { 344 my$self = shift;
345   $self->SUPER::_check(@_); 346$self->Error("You are not allowed to use intervals or sets in this context") if $self->{open}; 347 } 348 349 ################################################## 350 # 351 # Override the string and TeX methods 352 # so that we can strinfigy as inequalities 353 # rather than intervals. 354 # 355 package Inequalities::Interval; 356 our @ISA = ("Value::Interval"); 357 358 sub new { 359 my$self = shift; $self =$self->SUPER::new(@_);
360   $self->{isValue} = 1; 361 return$self;
362 }
363
364 sub make {
365   my $self = shift;$self = $self->SUPER::make(@_); 366$self->{isValue} = 1;
367   return $self; 368 } 369 370 sub string { 371 my$self = shift;
372   return $self->SUPER::string(@_) unless$self->getFlag('stringifyAsInequalities');
373   my ($a,$b,$open,$close) = $self->value; 374 my$x = ($self->context->variables->names)[0]; 375$x = $context->{variables}{$x}{string} if defined $context->{variables}{$x}{string};
376   my $left = ($open  eq '(' ? ' < ' : ' <= ');
377   my $right = ($close eq ')' ? ' < ' : ' <= ');
378   my $inequality = ""; 379$inequality .= $a->string.$left unless $self->{leftInfinite}; 380$inequality .= $x; 381$inequality .= $right.$b->string unless $self->{rightInfinite}; 382$inequality = "-infinity < $x < infinity" if$inequality eq $x; 383 return$inequality;
384 }
385
386 sub TeX {
387   my $self = shift; 388 return$self->SUPER::TeX(@_) unless $self->getFlag('stringifyAsInequalities'); 389 my ($a,$b,$open,$close) =$self->value;
390   my $context =$self->context;
391   my $x = ($context->variables->names)[0];
392   $x =$context->{variables}{$x}{TeX} if defined$context->{variables}{$x}{TeX}; 393$x =~ s/^([^_]+)_?(\d+)$/$1_{$2}/; 394 my$left  = ($open eq '(' ? ' < ' : ' <= '); 395 my$right = ($close eq ')' ? ' < ' : ' <= '); 396 my$inequality = "";
397   $inequality .=$a->string.$left unless$self->{leftInfinite};
398   $inequality .=$x;
399   $inequality .=$right.$b->string unless$self->{rightInfinite};
400   $inequality = "-\\infty <$x < \\infty " if $inequality eq$x;
401   return $inequality; 402 } 403 404 sub cmp_class {"an Inequality"} 405 406 ################################################## 407 # 408 # Override the string and TeX methods 409 # so that we can strinfigy as inequalities 410 # rather than unions. 411 # 412 package Inequalities::Union; 413 our @ISA = ("Value::Union"); 414 415 sub new { 416 my$self = shift; $self =$self->SUPER::new(@_);
417   $self->{isValue} = 1; 418 return$self;
419 }
420
421 sub make {
422   my $self = shift;$self = $self->SUPER::make(@_); 423$self->{isValue} = 1;
424   return $self; 425 } 426 427 sub string { 428 my$self = shift;
429   return $self->SUPER::string(@_) unless$self->getFlag('stringifyAsInequality');
430   my $equation = shift; shift; shift; my$prec = shift;
431   my $op = ($equation->{context} || $self->context)->{operators}{'or'}; 432 my @intervals = (); 433 foreach my$x (@{$self->data}) { 434$x->{format} = $self->{format} if defined$self->{format};
435     push(@intervals,$x->string($equation))
436   }
437   my $string = join($op->{string} || ' or ',@intervals);
438   $string = '('.$string.')' if $prec > ($op->{precedence} || 1.5);
439   return $string; 440 } 441 442 sub TeX { 443 my$self = shift;
444   return $self->SUPER::TeX(@_) unless$self->getFlag('stringifyAsInequality');
445   my $equation = shift; shift; shift; my$prec = shift;
446   my $op = ($equation->{context} || $self->context)->{operators}{'or'}; 447 my @intervals = (); 448 foreach my$x (@{$self->data}) {push(@intervals,$x->TeX($equation))} 449 my$TeX = join($op->{TeX} ||$op->{string} || ' or ',@intervals);
450   $TeX = '\left('.$TeX.'\right)' if $prec > ($op->{precedence} || 1.5);
451   return $TeX; 452 } 453 454 sub cmp_class {"an Inequality"} 455 456 ################################################## 457 # 458 # Override the string and TeX methods 459 # so that we can strinfigy as inequalities 460 # rather than sets. 461 # 462 package Inequalities::Set; 463 our @ISA = ("Value::Set"); 464 465 sub new { 466 my$self = shift; $self =$self->SUPER::new(@_);
467   $self->{isValue} = 1; 468 return$self;
469 }
470
471 sub make {
472   my $self = shift;$self = $self->SUPER::make(@_); 473$self->{isValue} = 1;
474   return $self; 475 } 476 477 sub string { 478 my$self = shift;  my $equation = shift; 479 return$self->SUPER::string($equation,@_) unless$self->getFlag('stringifyAsInequality');
480   my $x = ($self->context->variables->names)[0];
481   $x =$context->{variables}{$x}{string} if defined$context->{variables}{$x}{string}; 482 my @coords = (); 483 foreach my$a (@{$self->data}) { 484 if (Value::isValue($a)) {
485       $a->{format} =$self->{format} if defined $self->{format}; 486 push(@coords,$x.' = '.$a->string($equation));
487     } else {
488       push(@coords,$x.' = '.$a);
489     }
490   }
491   return $self->getFlag('noneWord') unless scalar(@coords); 492 return join(" or ",@coords); 493 } 494 495 sub TeX { 496 my$self = shift;  my $equation = shift; 497 return$self->SUPER::TeX($equation,@_) unless$self->getFlag('stringifyAsInequality');
498   my $x = ($self->context->variables->names)[0];
499   $x =$context->{variables}{$x}{TeX} if defined$context->{variables}{$x}{TeX}; 500$x =~ s/^([^_]+)_?(\d+)$/$1_{$2}/; 501 my @coords = (); 502 foreach my$a (@{$self->data}) { 503 if (Value::isValue($a)) {
504       $a->{format} =$self->{format} if defined $self->{format}; 505 push(@coords,$x.' = '.$a->TeX($equation));
506     } else {
507       push(@coords,$x.' = '.$a);
508     }
509   }
510   return '\hbox{'.\$self->getFlag('noneWord').'}' unless scalar(@coords);
511   return join(" or ",@coords);
512 }
513
514 sub cmp_class {"an Equality"}
515
516 ##################################################
517
518 1;