[system] / trunk / pg / macros / contextInequalities.pl Repository:
ViewVC logotype

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6505 - (download) (as text) (annotate)
Thu Nov 11 15:39:00 2010 UTC (9 years, 2 months ago) by gage
File size: 29172 byte(s)
moved files around
separated contextInequalities.pl to see if that would remove error messages


    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 #  placed in lib/Inequalities/common.pm
  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