[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 6145 - (download) (as text) (annotate)
Sat Oct 10 13:17:17 2009 UTC (10 years, 2 months ago) by dpvc
File size: 11667 byte(s)
A new context for chemical reactions

    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 sub _contextReaction_init {context::Reaction::Init()}
   70 
   71 ######################################################################
   72 
   73 package context::Reaction;
   74 our @ISA = ('Value::Formula');
   75 
   76 our $ELEMENT  = {isValue => 1, type => Value::Type("Element",1)};
   77 our $MOLECULE = {isValue => 1, type => Value::Type("Molecule",1)};
   78 our $COMPOUND = {isValue => 1, type => Value::Type("Compound",1)};
   79 our $REACTION = {isValue => 1, type => Value::Type("Reaction",1)};
   80 our $CONSTANT = {isValue => 1, type => Value::Type("Constant",1)};
   81 
   82 sub Init {
   83   my $context = $main::context{Reaction} = Parser::Context->getCopy("Numeric");
   84   $context->functions->clear();
   85   $context->strings->clear();
   86   $context->constants->clear();
   87   $context->lists->clear();
   88   $context->lists->add(
   89    'List' => {class =>'context::Reaction::List::List', open => '', close => '', separator => ' + '},
   90   );
   91   $context->parens->clear();
   92   $context->parens->add(
   93    '(' => {close => ')', type => 'List', formList => 1, removable => 1},
   94    '{' => {close => '}', type => 'List', removable => 1},
   95   );
   96   $context->operators->clear();
   97   $context->operators->set(
   98    '-->' => {precedence => 1, associativity => 'left', type => 'bin', string => ' --> ',
   99            class => 'context::Reaction::BOP::arrow', TeX => " \\longrightarrow "},
  100 
  101    '+' => {precedence => 2, associativity => 'left', type => 'bin', string => ' + ',
  102            class => 'context::Reaction::BOP::add', isComma => 1},
  103 
  104    ' ' => {precedence => 3, associativity => 'left', type => 'bin', string => ' ',
  105            class => 'context::Reaction::BOP::multiply', hidden => 1},
  106 
  107    '_' => {precedence => 4, associativity => 'left', type => 'bin', string => '_',
  108            class => 'context::Reaction::BOP::underscore'},
  109 
  110    '-' => {precedence => 5, associativity => 'left', type => 'both', string => ' - ',
  111            class => 'Parser::BOP::undefined'},
  112    'u-'=> {precedence => 6, associativity => 'left', type => 'unary', string => '-',
  113            class => 'Parser::UOP::undefined', hidden => 1},
  114   );
  115   $context->variables->are(
  116     map {$_ => $ELEMENT} (
  117       "H",                                                                                   "He",
  118       "Li","Be",                                                    "B", "C", "N", "O", "F", "Ne",
  119       "Na","Mg",                                                    "Al","Si","P", "S", "Cl","Ar",
  120       "K", "Ca",  "Sc","Ti","V", "Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr",
  121       "Rb","Sr",  "Y", "Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I", "Xe",
  122       "Cs","Ba",  "Lu","Hf","Ta","W", "Re","Os","Ir","Pt","Au","Hg","Ti","Pb"."Bi","Po","At","Rn",
  123       "Fr","Ra",  "Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Cn","Uut","Uuq","Uup","Uuh","Uus","Uuo",
  124 
  125                   "La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",
  126                   "Ac","Th","Pa","U", "Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No",
  127     )
  128   );
  129   $context->{parser}{Number} = "context::Reaction::Number";
  130   $context->{parser}{Variable} = "context::Reaction::Variable";
  131   $context->{parser}{List} = "context::Reaction::List";
  132   $context->{parser}{Formula} = "context::Reaction";
  133   $context->{value}{Reaction} = "context::Reaction";
  134   $context->{value}{Element} = "context::Reaction::Variable";
  135   $context->{value}{Constant} = "context::Reaction::Variable";
  136   Parser::Number::NoDecimals($context);
  137 
  138   main::PG_restricted_eval('sub Reaction {Value->Package("Formula")->new(@_)};');
  139 }
  140 
  141 sub compare {
  142   my ($l,$r) = @_; my $self = $l;
  143   my $context = $self->context;
  144   $r = $context->Package("Formula")->new($context,$r) unless Value::isFormula($r);
  145   return ($l->{tree}->equivalent($r->{tree}) ? 0 : 1);
  146 }
  147 
  148 sub eval {
  149   my $self = shift;
  150   $self->Error("Can't evaluate ".$self->TYPE);
  151 }
  152 
  153 sub TYPE {'a chemical reaction'}
  154 sub cmp_class {'a Chemical Reaction'}
  155 
  156 sub cmp_defaults {(showTypeWarnings => 1)}
  157 
  158 sub cmp_equal {Value::cmp_equal(@_)};
  159 
  160 sub cmp_postprocess {
  161   my $self = shift; my $ans = shift;
  162   return unless $self->{tree}->type eq 'Reaction';
  163   $self->cmp_Error($ans,"Your answer doesn't seem to be a reaction (it should contain a reaction arrow '-->')")
  164     unless $ans->{student_value}{tree}->type eq 'Reaction';
  165 }
  166 
  167 sub typeMatch {
  168   my $self = shift; my $other = shift;
  169   return 1;
  170 }
  171 
  172 ######################################################################
  173 
  174 package context::Reaction::Number;
  175 our @ISA = ('Parser::Number');
  176 
  177 sub equivalent {
  178   my $self = shift; my $other = shift;
  179   return 0 unless $other->class eq 'Number';
  180   return $self->eval == $other->eval;
  181 }
  182 
  183 sub isChemical {0}
  184 
  185 sub class {'Number'}
  186 sub TYPE {'a Number'}
  187 
  188 ######################################################################
  189 
  190 package context::Reaction::Variable;
  191 our @ISA = ('Parser::Variable');
  192 
  193 sub equivalent {
  194   my $self = shift; my $other = shift;
  195   return 0 unless $other->class eq 'Variable';
  196   return $self->{name} eq $other->{name};
  197 }
  198 
  199 sub eval {context::Reaction::eval(@_)}
  200 
  201 sub isChemical {1}
  202 
  203 sub TeX {
  204   my $self = shift;
  205   return "{\\rm $self->{name}}";
  206 }
  207 
  208 sub class {'Variable'}
  209 
  210 sub TYPE {
  211   my $self = shift;
  212   return ($self->type eq 'Constant'? "'$self->{name}'" : 'an element');
  213 }
  214 
  215 ######################################################################
  216 
  217 package context::Reaction::BOP;
  218 our @ISA = ('Parser::BOP');
  219 
  220 sub isChemical {1}
  221 
  222 sub eval {context::Reaction::eval(@_)}
  223 
  224 sub equivalent {
  225   my $self = shift; my $other = shift;
  226   return 0 unless substr($other->class,0,3) eq 'BOP';
  227   return $self->{lop}->equivalent($other->{lop}) && $self->{rop}->equivalent($other->{rop});
  228 }
  229 
  230 sub class {
  231   my $self = shift;
  232   my $class = ref($self);
  233   $class =~ s/.*::BOP::/BOP::/;
  234   return $class;
  235 }
  236 
  237 ######################################################################
  238 
  239 package context::Reaction::BOP::arrow;
  240 our @ISA = ('context::Reaction::BOP');
  241 
  242 sub isChemical {0}
  243 
  244 sub _check {
  245   my $self = shift;
  246   $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s",
  247                $self->{lop}->TYPE) unless $self->{lop}->isChemical;
  248   $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s",
  249                $self->{rop}->TYPE) unless $self->{rop}->isChemical;
  250   $self->{type} = $REACTION->{type};
  251 }
  252 
  253 sub TYPE {'a reaction'}
  254 
  255 ######################################################################
  256 
  257 package context::Reaction::BOP::add;
  258 our @ISA = ('Parser::BOP::comma','context::Reaction::BOP');
  259 
  260 sub _check {
  261   my $self = shift;
  262   $self->Error("Can't add %s and %s",$self->{lop}->TYPE,$self->{rop}->TYPE)
  263      unless $self->{lop}->isChemical && $self->{rop}->isChemical;
  264   $self->SUPER::_check(@_);
  265 }
  266 
  267 sub equivalent {
  268   my $self = shift; my $other = shift;
  269   return 0 unless substr($other->class,0,3) eq 'BOP';
  270   return $self->SUPER::equivalent($other) ||
  271          ($self->{lop}->equivalent($other->{rop}) && $self->{rop}->equivalent($other->{rop}));
  272 }
  273 
  274 sub TYPE {'a sum of Compounds'}
  275 
  276 ######################################################################
  277 
  278 package context::Reaction::BOP::multiply;
  279 our @ISA = ('context::Reaction::BOP');
  280 
  281 sub _check {
  282   my $self = shift;
  283   $self->Error("Can't combine %s and %s",$self->{lop}->TYPE,$self->{rop}->TYPE)
  284     unless ($self->{lop}->class eq 'Number' || $self->{lop}->isChemical) &&
  285             $self->{rop}->isChemical;
  286   $self->Error("Can't combine %s with %s",$self->{lop}{name},$self->{rop}->TYPE)
  287     if $self->{lop}->type eq 'Constant';
  288   $self->Error("Can't combine %s with %s",$self->{lop}->TYPE,$self->{rop}{name})
  289     if $self->{rop}->type eq 'Constant';
  290   $self->{type} = $COMPOUND->{type};
  291 }
  292 
  293 #
  294 #  No space for implied multiplication
  295 #
  296 sub string {
  297   my $self = shift;
  298   return $self->{lop}->string.$self->{rop}->string;
  299 }
  300 sub TeX {
  301   my $self = shift;
  302   return $self->{lop}->TeX.$self->{rop}->TeX;
  303 }
  304 
  305 sub TYPE {'a compound'}
  306 
  307 ######################################################################
  308 
  309 package context::Reaction::BOP::underscore;
  310 our @ISA = ('context::Reaction::BOP');
  311 
  312 sub _check {
  313   my $self = shift;
  314   $self->Error("The left-hand side of '_' must be an element, not %s",$self->{lop}->TYPE)
  315     unless $self->{lop}->type eq 'Element';
  316   $self->Error("The right-hand side of '_' must be a number, not %s",$self->{rop}->TYPE)
  317     unless $self->{rop}->class eq 'Number';
  318   $self->{type} = $MOLECULE->{type};
  319 }
  320 
  321 sub TeX {
  322   my $self = shift;
  323   return $self->{lop}->TeX."_{".$self->{rop}->TeX."}";
  324 }
  325 
  326 sub TYPE {'a molecule'}
  327 
  328 ######################################################################
  329 
  330 package context::Reaction::List;
  331 our @ISA = ('Parser::List::List');
  332 
  333 sub cmp_compare {
  334   my $self = shift; my $other = shift; my $ans = shift;
  335   return $self->{tree}->equivalent($other->{tree});
  336 }
  337 
  338 ######################################################################
  339 
  340 package context::Reaction::List::List;
  341 our @ISA = ('Parser::List::List');
  342 
  343 sub equivalent {
  344   my $self = shift; my $other = shift;
  345   return 0 unless $self->length == $other->length;
  346   my @left = main::lex_sort(map {$_->string} @{$self->{coords}});
  347   my @right = main::lex_sort(map {$_->string} @{$other->{coords}});
  348   return join(',',@left) eq join(',',@right);
  349 }
  350 
  351 sub TeX {
  352   my $self = shift; my $precedence = shift; my @coords = ();
  353   foreach my $x (@{$self->{coords}}) {push(@coords,$x->TeX)}
  354   return join(' + ',@coords);
  355 }
  356 
  357 sub eval {context::Reaction::eval(@_)}
  358 
  359 sub isChemical {1}
  360 
  361 sub TYPE {'a sum of compounds'}
  362 
  363 ######################################################################
  364 
  365 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9