[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 6213 - (download) (as text) (annotate)
Mon Mar 22 11:01:55 2010 UTC (9 years, 10 months ago) by dpvc
File size: 28896 byte(s)
Add =< and => operators and flag to control whether they are allowed or not.  Added more documentation

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9