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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6786 - (download) (as text) (annotate)
Sat Apr 9 18:16:09 2011 UTC (8 years, 9 months ago) by gage
File size: 13965 byte(s)
allow subscripts on compounds as well as elements.


    1 =head1 NAME
    2 
    3 contextReaction.pl - Implements a MathObject class for checmical reactions.
    4 
    5 =head1 DESCRIPTION
    6 
    7 This file implements a Context in which checmical reactions can be
    8 specified and compared.  Reactions can be composed of sums of integer
    9 multiples of elements (possibly with subscripts), separated by a right
   10 arrow (indicated by "-->").  Helpful error messages are given when a
   11 reaction is not of the correct form.  Sums of compounds can be given
   12 in any order, but the elements within a compound must be in the order
   13 given by the correct answer; e.g., if the correct answer specifies
   14 CO_2, then O_2C would be marked incorrect.
   15 
   16 To use the context include
   17 
   18   loadMacros("contextReaction.pl");
   19   Context("Reaction");
   20 
   21 at the top of your PG file, then create Formula() objects for your
   22 reactions.  For example:
   23 
   24   $R = Formula("4P + 5O_2 --> 2P_2O_5");
   25 
   26 Reactions know how to create their own TeX versions (via $R->TeX), and
   27 know how to check student answers (via $R->cmp), just like any other
   28 MathObject.
   29 
   30 The Reaction Context also allows you to create parts of reactions.
   31 E.g., you could create
   32 
   33   $products = Formula("4CO_2 + 6H_2O");
   34 
   35 which you could use in a problem as follows:
   36 
   37   loadMacros("contextReaction.pl");
   38   Context("Reaction");
   39 
   40   $reactants = Formula("2C_2H_6 + 7O_2");
   41   $products  = Formula("4CO_2 + 6H_2O");
   42 
   43   Context()->texStrings;
   44   BEGIN_TEXT
   45   \($reactants \longrightarrow\) \{ans_rule(30)\}.
   46   END_TEXT
   47   Context()->normalStrings;
   48 
   49   ANS($products->cmp);
   50 
   51 Note that sums and products are not simplified in any way, so that
   52 Formula("O + O") and Formula("2O") and Formula("O_2") are all
   53 different and unequal in this context.
   54 
   55 All the elements of the periodic table are available within the
   56 Reaction Context.  If you need additional terms, like "Heat" for
   57 example, you can add them as variables:
   58 
   59   Context()->variables->add(Heat => $context::Reaction::CONSTANT);
   60 
   61 Then you can make formulas that include Heat as a term.  These
   62 "constants" are not allowed to have coefficients or subscripts, and
   63 can not be combined with compounds except by addition.  If you want a
   64 term that can be combined in those ways, use
   65 $context::Reaction::ELEMENT instead.
   66 
   67 =cut
   68 
   69 ######################################################################
   70 
   71 sub _contextReaction_init {context::Reaction::Init()}
   72 
   73 ######################################################################
   74 #
   75 #  The main MathObject class for reactions
   76 #
   77 package context::Reaction;
   78 our @ISA = ('Value::Formula');
   79 
   80 #
   81 #  Some type declarations for the various classes
   82 #
   83 our $ELEMENT  = {isValue => 1, type => Value::Type("Element",1)};
   84 our $MOLECULE = {isValue => 1, type => Value::Type("Molecule",1)};
   85 our $COMPOUND = {isValue => 1, type => Value::Type("Compound",1)};
   86 our $REACTION = {isValue => 1, type => Value::Type("Reaction",1)};
   87 our $CONSTANT = {isValue => 1, type => Value::Type("Constant",1)};
   88 
   89 #
   90 #  Set up the context and Reaction() constructor
   91 #
   92 sub Init {
   93   my $context = $main::context{Reaction} = Parser::Context->getCopy("Numeric");
   94   $context->functions->clear();
   95   $context->strings->clear();
   96   $context->constants->clear();
   97   $context->lists->clear();
   98   $context->lists->add(
   99    'List' => {class =>'context::Reaction::List::List', open => '', close => '', separator => ' + '},
  100   );
  101   $context->parens->clear();
  102   $context->parens->add(
  103    '(' => {close => ')', type => 'List', formList => 1, removable => 1},
  104    '{' => {close => '}', type => 'List', removable => 1},
  105   );
  106   $context->operators->clear();
  107   $context->operators->set(
  108    '-->' => {precedence => 1, associativity => 'left', type => 'bin', string => ' --> ',
  109            class => 'context::Reaction::BOP::arrow', TeX => " \\longrightarrow "},
  110 
  111    '+' => {precedence => 2, associativity => 'left', type => 'bin', string => ' + ',
  112            class => 'context::Reaction::BOP::add', isComma => 1},
  113 
  114    ' ' => {precedence => 3, associativity => 'left', type => 'bin', string => ' ',
  115            class => 'context::Reaction::BOP::multiply', hidden => 1},
  116 
  117    '_' => {precedence => 4, associativity => 'left', type => 'bin', string => '_',
  118            class => 'context::Reaction::BOP::underscore'},
  119 
  120    '-' => {precedence => 5, associativity => 'left', type => 'both', string => ' - ',
  121            class => 'Parser::BOP::undefined'},
  122    'u-'=> {precedence => 6, associativity => 'left', type => 'unary', string => '-',
  123            class => 'Parser::UOP::undefined', hidden => 1},
  124   );
  125   $context->variables->are(
  126     map {$_ => $ELEMENT} (
  127       "H",                                                                                   "He",
  128       "Li","Be",                                                    "B", "C", "N", "O", "F", "Ne",
  129       "Na","Mg",                                                    "Al","Si","P", "S", "Cl","Ar",
  130       "K", "Ca",  "Sc","Ti","V", "Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr",
  131       "Rb","Sr",  "Y", "Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I", "Xe",
  132       "Cs","Ba",  "Lu","Hf","Ta","W", "Re","Os","Ir","Pt","Au","Hg","Ti","Pb","Bi","Po","At","Rn",
  133       "Fr","Ra",  "Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Cn","Uut","Uuq","Uup","Uuh","Uus","Uuo",
  134 
  135                   "La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",
  136                   "Ac","Th","Pa","U", "Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No",
  137     )
  138   );
  139   $context->{parser}{Number} = "context::Reaction::Number";
  140   $context->{parser}{Variable} = "context::Reaction::Variable";
  141   $context->{parser}{Formula} = "context::Reaction";
  142   $context->{value}{Reaction} = "context::Reaction";
  143   $context->{value}{Element} = "context::Reaction::Variable";
  144   $context->{value}{Constant} = "context::Reaction::Variable";
  145   Parser::Number::NoDecimals($context);
  146 
  147   main::PG_restricted_eval('sub Reaction {Value->Package("Formula")->new(@_)};');
  148 }
  149 
  150 #
  151 #  Compare by checking of the trees are equivalent
  152 #
  153 sub compare {
  154   my ($l,$r) = @_; my $self = $l;
  155   my $context = $self->context;
  156   $r = $context->Package("Formula")->new($context,$r) unless Value::isFormula($r);
  157   return ($l->{tree}->equivalent($r->{tree}) ? 0 : 1);
  158 }
  159 
  160 #
  161 #  Don't allow evaluation
  162 #
  163 sub eval {
  164   my $self = shift;
  165   $self->Error("Can't evaluate ".$self->TYPE);
  166 }
  167 
  168 #
  169 #  Provide a useful name
  170 #
  171 sub TYPE {'a chemical reaction'}
  172 sub cmp_class {'a Chemical Reaction'}
  173 
  174 #
  175 #  Set up the answer checker.  Avoid the list checker in
  176 #    Value::Formula::cmp_equal (for when the answer is a
  177 #    sum of compounds) and provide a postprocessor to
  178 #    give warnings when a reaction is compared to a
  179 #    student answer that isn't a reaction.
  180 #
  181 sub cmp_defaults {(showTypeWarnings => 1)}
  182 sub cmp_equal {Value::cmp_equal(@_)};
  183 sub cmp_postprocess {
  184   my $self = shift; my $ans = shift;
  185   return unless $self->{tree}->type eq 'Reaction';
  186   $self->cmp_Error($ans,"Your answer doesn't seem to be a reaction\n(it should contain a reaction arrow '-->')")
  187     if $ans->{showTypeWarnings} && $ans->{student_value}{tree}->type ne 'Reaction';
  188 }
  189 
  190 #
  191 #  Since the context only allows things that are comparable, we
  192 #  don't really have to check anything.  (But if somone added
  193 #  strings or constants, we would.)
  194 #
  195 sub typeMatch {
  196   my $self = shift; my $other = shift;
  197   return 1;
  198 }
  199 
  200 ######################################################################
  201 #
  202 #  The replacement for the Parser:Number class
  203 #
  204 package context::Reaction::Number;
  205 our @ISA = ('Parser::Number');
  206 
  207 #
  208 #  Equivalent is equal
  209 #
  210 sub equivalent {
  211   my $self = shift; my $other = shift;
  212   return 0 unless $other->class eq 'Number';
  213   return $self->eval == $other->eval;
  214 }
  215 
  216 sub isChemical {0}
  217 
  218 sub class {'Number'}
  219 sub TYPE {'a Number'}
  220 
  221 ######################################################################
  222 #
  223 #  The replacement for Parser::Variable.  We hold the elements here.
  224 #
  225 package context::Reaction::Variable;
  226 our @ISA = ('Parser::Variable');
  227 
  228 #
  229 #  Two elements are equivalent if their names are equal
  230 #
  231 sub equivalent {
  232   my $self = shift; my $other = shift;
  233   return 0 unless $other->class eq 'Variable';
  234   return $self->{name} eq $other->{name};
  235 }
  236 
  237 sub eval {context::Reaction::eval(@_)}
  238 
  239 sub isChemical {1}
  240 
  241 #
  242 #  Print element names in Roman
  243 #
  244 sub TeX {
  245   my $self = shift;
  246   return "{\\rm $self->{name}}";
  247 }
  248 
  249 sub class {'Variable'}
  250 
  251 #
  252 #  For a printable name, use a constant's name,
  253 #  and 'an element' for an element.
  254 #
  255 sub TYPE {
  256   my $self = shift;
  257   return ($self->type eq 'Constant'? "'$self->{name}'" : 'an element');
  258 }
  259 
  260 ######################################################################
  261 #
  262 #  General binary operator (add, multiply, arrow, and underscore
  263 #  are subclasses of this).
  264 #
  265 package context::Reaction::BOP;
  266 our @ISA = ('Parser::BOP');
  267 
  268 #
  269 #  Binary operators produce chemcicals (unless overridden, as in arrow)
  270 #
  271 sub isChemical {1}
  272 
  273 sub eval {context::Reaction::eval(@_)}
  274 
  275 #
  276 #  Two nodes are equivalent if their operands are equivalent
  277 #  and they have the same operator
  278 #
  279 sub equivalent {
  280   my $self = shift; my $other = shift;
  281   return 0 unless $other->class eq 'BOP';
  282   return 0 unless $self->{bop} eq $other->{bop};
  283   return $self->{lop}->equivalent($other->{lop}) && $self->{rop}->equivalent($other->{rop});
  284 }
  285 
  286 ######################################################################
  287 #
  288 #  Implements the --> operator
  289 #
  290 package context::Reaction::BOP::arrow;
  291 our @ISA = ('context::Reaction::BOP');
  292 
  293 #
  294 #  It is a reaction, not a chemical
  295 #
  296 sub isChemical {0}
  297 
  298 #
  299 #  Check that the operands are correct.
  300 #
  301 sub _check {
  302   my $self = shift;
  303   $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s",
  304                $self->{lop}->TYPE) unless $self->{lop}->isChemical;
  305   $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s",
  306                $self->{rop}->TYPE) unless $self->{rop}->isChemical;
  307   $self->{type} = $REACTION->{type};
  308 }
  309 
  310 sub TYPE {'a reaction'}
  311 
  312 ######################################################################
  313 #
  314 #  Implements addition, which forms a list of operands, so acts like
  315 #  the Parser::BOP::comma operator
  316 #
  317 package context::Reaction::BOP::add;
  318 our @ISA = ('Parser::BOP::comma','context::Reaction::BOP');
  319 
  320 #
  321 #  Check that the operands are OK
  322 #
  323 sub _check {
  324   my $self = shift;
  325   $self->Error("Can't add %s and %s",$self->{lop}->TYPE,$self->{rop}->TYPE)
  326      unless $self->{lop}->isChemical && $self->{rop}->isChemical;
  327   $self->SUPER::_check(@_);
  328 }
  329 
  330 #
  331 #  Two are equivalent if they are equivalent in either order.
  332 #  (never really gets used, since these result in the creation
  333 #  of a list rather than an "add" node in the final tree.
  334 #
  335 sub equivalent {
  336   my $self = shift; my $other = shift;
  337   return 0 unless substr($other->class,0,3) eq 'BOP';
  338   return $self->SUPER::equivalent($other) ||
  339          ($self->{lop}->equivalent($other->{rop}) && $self->{rop}->equivalent($other->{rop}));
  340 }
  341 
  342 sub TYPE {'a sum of Compounds'}
  343 
  344 ######################################################################
  345 #
  346 #  Implements concatenation, which produces compounds or integer
  347 #  multiples of elements or molecules.
  348 #
  349 package context::Reaction::BOP::multiply;
  350 our @ISA = ('context::Reaction::BOP');
  351 
  352 #
  353 #  Check that the operands are OK
  354 #
  355 sub _check {
  356   my $self = shift;
  357   $self->Error("Can't combine %s and %s",$self->{lop}->TYPE,$self->{rop}->TYPE)
  358     unless ($self->{lop}->class eq 'Number' || $self->{lop}->isChemical) &&
  359             $self->{rop}->isChemical;
  360   $self->Error("Can't combine %s with %s",$self->{lop}{name},$self->{rop}->TYPE)
  361     if $self->{lop}->type eq 'Constant';
  362   $self->Error("Can't combine %s with %s",$self->{lop}->TYPE,$self->{rop}{name})
  363     if $self->{rop}->type eq 'Constant';
  364   $self->{type} = $COMPOUND->{type};
  365 }
  366 
  367 #
  368 #  No space in output for implied multiplication
  369 #
  370 sub string {
  371   my $self = shift;
  372   return $self->{lop}->string.$self->{rop}->string;
  373 }
  374 sub TeX {
  375   my $self = shift;
  376   return $self->{lop}->TeX.$self->{rop}->TeX;
  377 }
  378 
  379 sub TYPE {'a compound'}
  380 
  381 ######################################################################
  382 #
  383 #  Implements the underscore for creating molecules
  384 #
  385 package context::Reaction::BOP::underscore;
  386 our @ISA = ('context::Reaction::BOP');
  387 
  388 #
  389 #  Check that the operands are OK
  390 #
  391 sub _check {
  392   my $self = shift;
  393   $self->Error("The left-hand side of '_' must be an element or compound, not %s",$self->{lop}->TYPE)
  394     unless $self->{lop}->type eq 'Element' || $self->{lop}->type eq 'Compound';
  395   $self->Error("The right-hand side of '_' must be a number, not %s",$self->{rop}->TYPE)
  396     unless $self->{rop}->class eq 'Number';
  397   $self->{type} = $MOLECULE->{type};
  398 }
  399 
  400 #
  401 #  Create proper TeX output
  402 #
  403 sub TeX {
  404   my $self = shift;
  405   my $left = $self->{lop}->TeX;
  406   $left = "($left)" if $self->{lop}->type eq 'Compound';
  407   return $left."_{".$self->{rop}->TeX."}";
  408 }
  409 
  410 #
  411 #  Create proper text output
  412 #
  413 sub string {
  414   my $self = shift;
  415   my $left = $self->{lop}->string;
  416   $left = "($left)" if $self->{lop}->type eq 'Compound';
  417   return $left."_".$self->{rop}->string;
  418 }
  419 
  420 sub TYPE {'a molecule'}
  421 
  422 ######################################################################
  423 #
  424 #  Implements sums of compounds as a list
  425 #
  426 package context::Reaction::List::List;
  427 our @ISA = ('Parser::List::List');
  428 
  429 #
  430 #  Two sums are equivalent if their terms agree in any order.
  431 #  (we check by stringifying them and sorting, then compare results)
  432 #
  433 sub equivalent {
  434   my $self = shift; my $other = shift;
  435   return 0 unless $self->length == $other->length;
  436   my @left = main::lex_sort(map {$_->string} @{$self->{coords}});
  437   my @right = main::lex_sort(map {$_->string} @{$other->{coords}});
  438   return join(',',@left) eq join(',',@right);
  439 }
  440 
  441 #
  442 #  Use "+" between entries in the list (with no parens)
  443 #
  444 sub TeX {
  445   my $self = shift; my $precedence = shift; my @coords = ();
  446   foreach my $x (@{$self->{coords}}) {push(@coords,$x->TeX)}
  447   return join(' + ',@coords);
  448 }
  449 
  450 sub eval {context::Reaction::eval(@_)}
  451 
  452 sub isChemical {1}
  453 
  454 sub TYPE {'a sum of compounds'}
  455 
  456 ######################################################################
  457 
  458 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9