From: Mike G. v. a. <we...@ma...> - 2008-06-24 02:23:08
|
Log Message: ----------- Backporting MathObject documentation to rel-2-4-5 Tags: ---- rel-2-4-patches Added Files: ----------- pg/doc/MathObjects: MathObjectsAnswerCheckers.pod README.pod UsingMathObjects.pod pg/doc/MathObjects/extensions: 1-function.pg 2-function.pg 3-operator.pg 4-list.pg 5-operator.pg 6-precedence.pg 7-context.pg 8-answer.pg pg/doc/MathObjects/macros: Differentiation.pl DifferentiationDefs.pl parserTables.pl parserUtils.pl unionImage.pl unionTables.pl pg/doc/MathObjects/problems: sample01.pg sample02.pg sample03.pg sample04.pg sample05.pg sample06.pg sample07.pg sample08.pg sample09.pg sample10.pg sample11.pg sample12.pg sample13.pg sample14.pg sample15.pg sample16.pg sample17.pg sample18.pg sample19.pg sample20.pg sample21.pg sample22.pg Revision Data ------------- --- /dev/null +++ doc/MathObjects/UsingMathObjects.pod @@ -0,0 +1,393 @@ +=head1 Using MathObjects + +To use MathObjects in your own problems, you need to load the +"MathObjects.pl" macro file: + + loadMacros("Parser.pl"); + +which defines the commands you need to interact with MathObjects. +Once you have done that, you can call the MathObjects functions to create +formulas for you. The main call is Formula(), which takes a string and +returns a parsed version of the string. For example: + + $f = Formula("x^2 + 3x + 1"); + +will set $f to a reference to the parsed version of the formula. + +=head2 Working With Formulas + +A formula has a number of methods that you can call. These include: + +=over + +=item $f->eval(x=>5) + +Evaluate the formula when x is 5. +If $f has more variables than that, then +you must provide additional values, as in +$f->eval(x=>3,y=>1/2); + +=item $f->reduce + +Tries to remove redundent items from your +formula. For example, Formula("1x+0") returns "x". +Reduce tries to factor out negatives and do +some other adjustments as well. (There still +needs to be more work done on this. What it does +is correct, but not always smart, and there need +to be many more situations covered.) All the +reduction rules can be individually enabled +or disabled using the Context()->reduction->set() +method, but the documentation for the various +rules is not yet ready. + +=item $f->substitute(x=>5) + +Replace x by the value 5 throughout (you may want +to reduce the result afterword, as this is not +done automatically). Note that you can replace a +variable by another formula, if you wish. To make +this easier, substitute will apply Formula() to +any string values automatically. E.g., + Formula("x-1")->substitute(x=>"y") +returns "y-1" as a formula. + +=item $f->string + +returns a string representation of the formula +(should be equivalent to the original, though not +necessarily equal to it). + +=item $f->TeX + +returns a LaTeX representation of the formula. +You can use this in BEGIN_TEXT...END_TEXT blocks +as follows: + + BEGIN_TEXT + Suppose \(f(x) = \{$f->TeX}\). ... + END_TEXT + +=item $f->perl + +returns a representation of the formula that could +be evaluated by perl's eval() function. + +=item $f->perlFunction + +returns a perl code block that can be called to +evaluate the function. For example: + + $f = Formula('x^2 + 3')->perlFunction; + $y = &$f(5); + +will assign the value 28 to $y. +You can also pass a function name to perlFunction +to get a named function to call: + + Formula('x^2 + 3')->perlFunction('f'); + $y = f(5); + +If the formula involves more than one variable, +then the paramaters should be given in +alphabetical order. + + Formula('x^2 + y')->perlFunction('f'); + $z = f(5,3); # $z is 28. + +Alternatively, you can tell the order for the +parameters: + + Formula('x^2 + y')->perlFunction('f',['y','x']); + $z = f(5,3); $ now $z is 14. + +=back + +=head2 Combining Formulas + +There is a second way to create formulas. Once you have a formula, you can +create additional formulas simply by using perls' built-in operations and +functions, which have been overloaded to handle formulas. For example, + + $x = Formula('x'); + $f = 3*x**2 + 2*$x - 1; + +makes $f be a formula, and is equivalent to having done + + $f = Formula("3x^2 + 2x - 1"); + +This can be very convenient, but also has some pitfalls. First, you +need to include '*' for multiplication, since perl doesn't do implied +multiplication, and you must remember to use '**' not '^'. (If you use '^' +on a formula, the parser will remind you to use '**'.) Second, the +precedences of the operators in perl are fixed, and so changes you make to +the precedence table for the parser are not reflected in formulas produced +in this way. (The reason '^' is not overloaded to do exponentiation is +that the precedence of '^' is wrong for that in perl, and can't be +changed.) As long as you leave the default precedences, however, things +should work as you expect. + +Note that the standard functions, like sin, cos, etc, are overloaded to +generate appropriate formulas when their values are formulas. For example, + + $x = Formula('x'); + $f = cos(3*$x + 1); + +produces the same result as $f = Formula("cos(3x+1)"); and you can then go +on to output its TeX form, etc. + +=head2 Special Syntax + +This parser has support for some things that are missing from the current +one, like absolute values. You can say |1+x| rather than abs(1+x) +(though both are allowed), and even |1 - |x|| works. + +Also, you can use sin^2(x) (or even sin^2 x) to get (sin(x))^2. + +Finally, you can use sin^-1(x) to get arcsin(x). + +There is an experimental set of operator precedences that make it possible +to write sin 2x + 3 and get sin(2x) + 3. See examples/7-precedence.pg +for some details. + +=head2 The Formula Types + +The parser understands a wide range of data types, including real and +complex numbers, points, vectors, matrices, arbitrary lists, intervals, +unions of intervals, and predefined words. Each has a syntax for use +within formulas, as described below: + + numbers the usual form: 153, 233.5, -2.456E-3, etc. + + complex a + b i where a and b are numbers: 1+i, -5i, 6-7i, etc. + + infinitites the words 'infinity' or '-infinity' (or several + equivalents). + + point (a,b,c) where a, b and c are real or complex numbers. + any number of coordinates are allowed. Eg, (1,2), + (1,0,0,0), (-1,2,-3). Points are promoted to vectors + automatically, when necessary. + + vector <a,b,c> or a i + b j + c k (when used in vector context). + As with points, vectors can have any number of + coordinates. For example, <1,0,0>, <-1,3>, <x,1-x>, etc. + + matrix [[a11,...,a1n],...[am1,...amn]], i.e., use [..] around + each row, and around the matrix itself. The elements + are separated by commas (not spaces). e.g, + [[1,2],[3,4]] (a 2x2 matrix) + [1,2] (a 1x2 matrix, really a vector) + [[1],[2]] (a 2x1 matrix, ie. column vector) + Points and vectors are promoted to matrices when + appropriate. Vectors are converted to column vectors + when needed for matrix-vector multiplication. Matrices + can be 3-dimensional or higher by repeated nesting of + matrices. (In this way, a 2-dimensional matrix is really + thought of as a vector of vectors, and n-dimensional + ones as vectors of (n-1)-dimensional ones.) + + list (a,b,c) where a,b,c are arbitrary elements. + For example, (1+i, -3, <1,2,3>, Infinity). + The empty list () is allowed, and the parentheses are + optional if there is only one list. (This makes it + possible to make list-based answer checkers that + really know where the separations occur.) + + interval (a,b), (a,b], [a,b), [a,b], or [a,a] where a and b are + numbers or appropriate forms of infinity. + For example, (-INF,3], [4,4], [2,INF), (-INF,INF). + + union represented by 'U'. For example [-1,0) U (0,1]. + + string special predefined strings like NONE and DNE. + +These forms are what are used in the strings passed to Formula(). +If you want to create versions of these in perl, there are several +ways to do it. One way is to use the Compute() command, which takes a +string parses it and then evaluates the result (it is equivalent to +Formula(...)->eval). If the formula produces a vector, the result +will be a Vector constant that you can use in perl formulas by hand. + +For example: + + $v = Compute("<1,1,0> >< <-1,4,-2>"); + +would compute the dot product of the two vectors and assign the +resulting vector object to $v. + +Another way to generate constants of the various types is to use the +following routines. If their inputs are constant, they produce a +constant of the appropriate type. If an input is a formula, they +produce corresponding formula objects. + + Real(a) create a real number with "fuzzy" + comparisons (so that 1.0000001 == Real(1) is true). + + Complex(a,b) create a complex number a + b i + + Infinity creates the +infinity object + -(Infinity) creates -infinity + + Point(x1,...xn) or Point([x1,...,xn]) produces (x1,...,xn) + + Vector(x1,...,xn) or Vector([x1,...,xn]) produces <x1,...,xn> + + Matrix([a11,...,a1m],...,[am1,...,amn]) or + Matrix([[a11,...,a1m],...,[am1,...,amn]]) produces an n x m matrix + + List(a,...,b) produces a list with the given elements + + Interval('(',a,b,']') produces (a,b], (the other endpoints work as + expected. Use 'INF' and '-INF' for infinities.) + + Union(I1,...,In) takes the union of the n intervals. (where I1 to In + are intervals.) + + String(word) Produces a string object for the given word (if it + is a known word). This is mostly to be able to + call the ->cmp and ->TeX methods. + +For example, + + $a = random(-5,5,1) + $V = Vector($a,1-$a,$a**2+1); + +produces a vector with some random coordinates. + +Objects of these types also have TeX, string and perl methods, so you can +use: + + Vector(1,2,3)->TeX + +to produce a TeX version of the vector, just as you can with formulas. + +There are several "constant" functions that generate common constant +values. These include pi, i, j, k and Infininty. you can use these +in perl expressions as though they were their actual values: + + $z = $a + $b * i; + $v = $a*i + $b*j + $c*k; + $I = Infinity; + +Note that because of a peculiarity of perl, you need to use -(pi) +or - pi (with a space) rather than -pi, and similary for the other +functions. Without this, you will get an error message about an +ambiguity being resolved. (This is not a problem if you process your +expressions through the parser itself, only if you are writing +expressions in perl directly. Note that since student answers are +processed by the parser, not perl directly, they can write -pi without +problems.) + +Another useful command is Compute(), which evaluates a formula and +returns its value. This is one way to create point or vector-valued +constants, but there is an easier way discussed below. + +=head2 Specifying the Context + +You may have noticed that "i" was used in two different ways in the +examples above. In the first example, it was treated as a complex +number and the second as a coordinate unit vector. To control which +interpretation is used, you specify a parser "context". + +The context controls what operations and functions are defined in the +parser, what variables and constants to allow, how to interpret +various paretheses, and so on. Changing the context can completely +change the way a formula is interpreted. + +There are several predefined contexts: Numeric, Complex, Vector, +Interval and Full. (You can also define your own contexts, but that +will be described elsewhere.) To select a context, use the Context() +function, e.g. + + Context("Numeric"); + +selects the numeric context, where i, j and k have no special meaning, +points and vectors can't be used, and the only predefined variable is +'x'. + +On the other hand, Context("Vector") makes i, j and k represent the +unit coordinate vectors, and defines variables 'x', 'y' and 'z'. + +Context("Interval") is like numeric context, but it also defines the +parentheses so that they will form intervals (rather than points or +lists). + +Once you have selected a context, you can modify it to suit the +particular needs of your problem. The command + + $context = Context(); + +gets you a reference to the current context object (you can also use +something like + + $context = Context("Numeric"); + +to set the context and get its reference at the same time). Once you +have this reference, you can call the Context methods to change values +in the context. These are discussed in more detail in the +documentation of the Context object [not yet written], but some of the +more common actions are described here. + +To add a variable, use, for example, + + $context->variables->add(y=>'Real'); + +To delete any existing variables and replace them with new ones, use + + $context->variables->are(t=>'Real'); + +To remove a variable, use + + $context->variables->remove('t'); + +To get the names of the defind variables, use + + @names = $context->variables->names; + + +Similarly, you can add a named constant via + + $context->constants->add(M=>1/log(10)); + +and can change, remove or list the constants via methods like those +used for variables above. The command + + $M = $constant->constants->get('M'); + +will return the value of the consant M. (See the +pg/lib/Value/Context/Data.pm file for more information on the methods +you can call for the various types of context data.) + +To add new predefined words (like 'NONE' and 'DNE'), use something +like + + $constant->strings->add(TRUE=>{},FALSE=>{}); + +Note that strings are case-sensitive, so you might want to add + + $constant->strings->add( + true => {alias=>'TRUE'}, + false => {alias=>'FALSE'}, + ); + +so that either "TRUE" or "true" will be interpreted as TRUE. + +There are a number of values stored in the context that control things +like the tolerance used when comparing numbers, and so on. You +control these via commands like: + + $context->flags->set(tolerance=>.00001); + +For example, + + $context->flags->set(ijk=>1); + +will cause the output of all vectors to be written in ijk format +rather than <...> format. + +Finally, you can add or modify the operators and functions that are +available in the parser via calls to $context->operators and +$context->functions. See the files in webwork2/docs/parser/extensions +for examples of how to do this. + --- /dev/null +++ doc/MathObjects/README.pod @@ -0,0 +1,197 @@ +=head1 NAME + +MathObjects - Object system for manipulating mathematics in PG. + +=head1 OVERVIEW + +This directory contains the documentation for a new +mathematical-expression parser written in perl. It was developed for +use with the WeBWorK on-line homework system, but it can be used in +any perl program. + +The goal was to process vector-valued expressions, but the parser was +designed to be extensible, so that you could add your own functions, +operators, and data types. It is still a work in progress, but should +provide a framework for building more sophisticated expression handling. + +Currenlty, the parser understands: + +=over + +=item * real and complex numbers, + +=item * points, vectors, and matrices (with real or complex entries) + +=item * arbitrary lists of elements + +=item * intervals and unions of intervals + +=item * predefined strings like 'infinity' + +=back + +Some other useful features are that you can write sin^2 x for (sin(x))^2 +and sin^-1 x for arcsin(x), and so on. + +Most of the documentation still needs to be written, but you can get some +ideas from the samples in the problems and extensions directories, and by +reading the files in this directory. + +=head1 INSTALLATION + +The parser should already be installed as part of the WeBWorK 2.1 +distribution, so you should not need to install it separately. If you +don't seem to have it installed, then it can be obtained from the +Union CVS repository at +http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/WeBWorKCVS + +The README file in that directory contains the installation instructions. + +=head1 SAMPLE FILES + +Sample problems are given in the problems and extensions directories. Move +these to the templates directory of a course where you want to test the +Parser, and move the contents of the macros directory to that course's +macros directory. + +Now try looking at these problems using the Library Browser. Edit the +source to see how they work, and to read the comments within the code +itself. + +=head1 EXAMPLE FILES + +The 'problems' directory contains several examples that show how to use +Parser within your problem files. + +=over + +=item problems/sample01.pg + +Uses the parser to make a string into a formula that you can +evaluate and print in TeX form + +=item problems/sample02.pg + +Shows how to create formulas using perl's usual mathematical +expressions rather than character strings. + +=item problems/sample03.pg + +Shows how to use the parser's differentiation abilities. + +=item problems/sample04.pg + +=item problems/sample05.pg + +Use the parser in conjunction with the graphics macros to generate +function graphs on the fly. These also show how to create a +perl function to evaluate an expression. + +=item problems/sample06.pg + +Shows some simple use of vectors in a problem. + +=item problems/sample07.pg + +Example if using the build-in Real object and its answer +checker + +=item problems/sample08.pg + +Uses complex numbers and the built-in checker + +=item problems/sample09.pg + +=item problems/sample10.pg + +Demonstrates points and vectors and their answer checkers + +=item problems/sample11.pg + +=item problems/sample12.pg + +Shows the answer checkers for intervals and unions. + +=item problems/sample13.pg + +=item problems/sample14.pg + +=item problems/sample15.pg + +Demonstrate various list checkers, including a check for the +word 'NONE', which is a predefined string. + +=item problems/sample16.pg + +=item problems/sample17.pg + +=item problems/sample18.pg + +These show the multi-variable function checker in use (for +functions of the form R->R, R^2->R and R->R^3). + +=item problems/sample19.pg + +Uses the function checker to implement a "constant" that can +be used in formulas. + +=item problems/sample20.pg + +Shows how to use the parser's substitution abilities. + +=item problems/sample21.pg + +Checks for a list of points. + +=item problems/sample22.pg + +Shows how to provide named constants that the student can +use in his answer. + +=back + +The 'examples' directory contains samples that show how to extend the +parser to include your own functions, operators, and so on. There are also +some samples of how to call the methods available for Formula objects +generated by the parser, and what some error messages look like. + +=over + +=item examples/1-function.pg + +Adds a single-variable function to the parsers list of functions. + +=item examples/2-function.pg + +Adds a two-variable function to the parser. + +=item examples/3-operator.pg + +Adds a binary operator to the parser. (Unary operators are similar.) + +=item examples/4-list.pg + +Adds a new "list type" object. In this case, it's really an +operation [n,r] that returns n choose r. + +=item examples/5-list.pg + +Add a new "equality" operator that you can use to handle answers +like "x+y=0". + +=item examples/6-precedence.pg + +Shows an experimental precedence setting that can be used to make +sin 2x return sin(2x) rather than (sin(2))x. + +=item examples/7-context.pg + +Shows how to switch contexts (in this case, to complex and to vector +contexts), and how this affects the parsing. + +=item examples/8-answer.pg + +Implements a simple vector-valued answer checker using the +parser's computation and comparison ability. + +=back --- /dev/null +++ doc/MathObjects/MathObjectsAnswerCheckers.pod @@ -0,0 +1,427 @@ +=head1 MathObjects-based Answer Checkers + +MathObjects are designed to be used in two ways. First, you can use it +within your perl code when writing problems as a means of making it +easier to handle formulas, and in particular, to be able to use a single +object to produce numeric values, TeX output and answer strings from a +single formula entry. This avoids having to type a function three +different ways (which makes maintaining a problem much harder). Since +MathObjects also included vector and complex arthimetic, it is easier to +work with these types of values as well. + +Secondly using MathObjects improves the processing of student input. +This is accomplished through special answer checkers that are part of +the Parser package (rather than the traditional WeBWorK answer +checkers). Each of these checkers has error checking customized to the +type of input expected from the student and can provide helpful feedback +if the syntax of the student's entry is incorrect. + +Checkers are available for each of the types of values that the parser +can produce (numbers, complex numbers, infinities, points, vectors, +intervals, unions, formulas, lists of numbers, lists of points, lists of +intervals, lists of formulas returning numbers, lists of formulas +returning points, and so on). + +To use one of these checkers, simply call the ->cmp method of the +object that represents the correct answer. For example: + + $n = Real(sqrt(2)); + ANS($n->cmp); + +will produce an answer checker that matches the square root of two. +Similarly, + + ANS(Vector(1,2,3)->cmp); + +matches the vector <1,2,3> (or any computation that produces it, e.g., +i+2j+3k, or <4,4,4>-<3,2,1>), while + + ANS(Interval("(-inf,3]")->cmp); + +matches the given interval. Other examples include: + + ANS(Infinity->cmp); + ANS(String('NONE')->cmp); + ANS(Union("(-inf,$a) U ($a,inf)")->cmp); + +and so on. + +Formulas are handled in the same way: + + ANS(Formula("x+1")->cmp); + + $a = random(-5,5,1); $b = random(-5,5,1); $x = random(-5,5,1); + $f = Formula("x^2 + $a x + $b")->reduce; + ANS($f->cmp); + ANS($f->eval(x=>$x)->cmp); + + $x = Formula('x'); + ANS((1+$a*$x)->cmp); + + Context("Vector")->variables->are(t=>'Real'); + $v = Formula("<t,t^2,t^3>"); $t = random(-5,5,1); + ANS($v->cmp); + ANS($v->eval(t=>$t)->cmp); + +and so on. + +Lists of items can be checked as easily: + + ANS(List(1,-1,0)->cmp); + ANS(List(Point($a,$b),Point($a,-$b))->cmp); + ANS(List(Vector(1,0,0),Vector(0,1,1))->cmp); + ANS(Compute("(-inf,2),(4,5)")->cmp); # easy way to get list of intervals + ANS(Formula("x, x+1, x^2-1")->cmp); + ANS(Formula("<x,2x>,<x,-2x>,<0,x>")->cmp); + ANS(List('NONE')->cmp); + +and so on. The last example may seem strange, as you could have used +ANS(String('NONE')->cmp), but there is a reason for using this type +of construction. You might be asking for one or more numbers (or +points, or whatever) or the word 'NONE' of there are no numbers (or +points). If you used String('NONE')->cmp, the student would get an +error message about a type mismatch if he entered a list of numbers, +but with List('NONE')->cmp, he will get appropriate error messages for +the wrong entries in the list. + +It is often appropriate to use the list checker in this way even when +the correct answer is a single value, if the student might type a list +of answers. + +On the other hand, using the list checker has its disadvantages. For +example, if you use + + ANS(Interval("(-inf,3]")->cmp); + +and the student enters (-inf,3), she will get a message indicating +that the type of interval is incorrect, while that would not be the +case if + + ANS(List(Interval("(-inf,3]"))->cmp); + +were used. (This is because the student doesn't know how many +intervals there are, so saying that the type of interval is wrong +would inform her that there is only one.) + +The rule of thumb is: the individual checkers can give more detailed +information about what is wrong with the student's answer; the list +checker allows a wider range of answers to be given without giving +away how many answers there are. If the student knows there's only +one, use the individual checker; if there may or may not be more than +one, use the list checker. + +Note that you can form lists of formulas as well. The following all +produce the same answer checker: + + ANS(List(Formula("x+1"),Formula("x-1"))->cmp); + + ANS(Formula("x+1,x-1")->cmp); # easier + + $f = Formula("x+1"); $g = Formula("x-1"); + ANS(List($f,$g)->cmp); + + $x = Formula('x'); + ANS(List($x+1,$x-1)->cmp); + +See the files in webwork2/doc/parser/problems for more +examples of using the parser's answer checkers. + +=head2 Controlling the Details of the Answer Checkers + +The action of the answer checkers can be modified by passing flags to +the cmp() method. For example: + + ANS(Real(pi)->cmp(showTypeWarnings=>0)); + +will prevent the answer checker from reporting errors due to the +student entering in the wrong type of answer (say a vector rather than +a number). + +=head3 Flags common to all answer checkers + +There are a number of flags common to all the checkers: + +=over + +=item S<C<< showTypeWarnings=>1 or 0 >>> + +show/don't show messages about student +answers not being of the right type. +(default: 1) + +=item S<C<< showEqualErrors=>1 or 0 >>> + +show/don't show messages produced by +trying to compare the professor and +student values for equality, e.g., +conversion errors between types. +(default: 1) + +=item S<C<< ignoreStrings=>1 or 0 >>> + +show/don't show type mismatch errors +produced by strings (so that 'NONE' will +not cause a type mismatch in a checker +looking for a list of numbers, for example). +(default: 1) + +=back + +In addition to these, the individual types have their own flags: + +=head3 Flags for Real()->cmp + +=over + +=item S<C<< ignoreInfinity=>1 or 0 >>> + +Don't report type mismatches if the +student enters an infinity. +(default: 1) + +=back + +=head3 Flags for String()->cmp + +=over + +=item S<C<< typeMatch=>value >>> + +Specifies the type of object that +the student should be allowed to enter +(in addition the string). +(default: 'Value::Real') + +=back + +=head3 Flags for Point()->cmp + +=over + +=item S<C<< showDimensionHints=>1 or 0 >>> + +show/don't show messages about the +wrong number of coordinates. +(default: 1) + +=item S<C<< showCoordinateHints=>1 or 0 >>> + +show/don't show message about +which coordinates are right. +(default: 1) + +=back + +=head3 Flags for Vector()->cmp + +=over + +=item S<C<< showDimensionHints=>1 or 0 >>> + +show/don't show messages about the +wrong number of coordinates. +(default: 1) + +=item S<C<< showCoordinateHints=>1 or 0 >>> + +show/don't show message about +which coordinates are right. +(default: 1) + +=item S<C<< promotePoints=>1 or 0 >>> + +do/don't allow the student to +enter a point rather than a vector. +(default: 1) + +=item S<C<< parallel=>1 or 0 >>> + +Mark the answer as correct if it +is parallel to the professor's answer. +Note that a value of 1 forces +showCoordinateHints to be 0. +(default: 0) + +=item S<C<< sameDirection=>1 or 0 >>> + +During a parallel check, mark the +answer as correct only if it is in +the same (not the opposite) +direction as the professor's answer. +(default: 0) + +=back + +=head3 Flags for Matrix()->cmp + +=over + +=item S<C<< showDimensionHints=>1 or 0 >>> + +show/don't show messages about the +wrong number of coordinates. +(default: 1) + +=back + +The default for showEqualErrors is set to 0 for Matrices, since +these errors usually are dimension errors, and that is handled +separately (and after the equality check). + +=head3 Flags for Interval()->cmp + +=over + +=item S<C<< showEndpointHints=>1 or 0 >>> + +do/don't show messages about which +endpoints are correct. +(default: 1) + +=item S<C<< showEndTypeHints=>1 or 0 >>> + +do/don't show messages about +whether the open/closed status of +the enpoints are correct (only +shown when the endpoints themselves +are correct). +(default: 1) + +=back + +=head3 Flags for Union()->cmp and List()->cmp + +all the flags from the Real()->cmp, plus: + +=over + +=item S<C<< showHints=>1 or 0 >>> + +do/don't show messages about which +entries are incorrect. +(default: $showPartialCorrectAnswers) + +=item S<C<< showLengthHints=>1 or 0 >>> + +do/don't show messages about having the +correct number of entries (only shown +when all the student answers are +correct but there are more needed, or +all the correct answsers are among the +ones given, but some extras were given). +(default: $showPartialCorrectAnswers) + +=item S<C<< partialCredit=>1 or 0 >>> + +do/don't give partial credit for when +some answers are right, but not all. +(default: $showPartialCorrectAnswers) +(currently the default is 0 since WW +can't handle partial credit properly). + +=item S<C<< ordered=>1 or 0 >>> + +give credit only if the student answers +are in the same order as the +professor's answers. +(default: 0) + +=item S<C<< entry_type=>'a (name)' >>> + +The string to use in error messages +about type mismatches. +(default: dynamically determined from list) + +=item S<C<< list_type=>'a (name)' >>> + +The string to use in error messages +about numbers of entries in the list. +(default: dynamically determined from list) + +=item S<C<< typeMatch=>value >>> + +Specifies the type of object that +the student should be allowed to enter +in the list (determines what +constitutes a type mismatch error). +(default: dynamically determined from list) + +=item S<C<< requireParenMatch=>1 or 0 >>> + +Do/don't require the parentheses in the +student's answer to match those in the +professor's answer exactly. +(default: 1) + +=item S<C<< removeParens=>1 or 0 >>> + +Do/don't remove the parentheses from the +professor's list as part of the correct +answer string. This is so that if you +use List() to create the list (which +doesn't allow you to control the parens +directly), you can still get a list +with no parentheses. +(default: 0 for List() and 1 for Formula()) + +=back + +=head3 Flags for Formula()->cmp + +The flags for formulas are dependent on the type of the result of +the formula. If the result is a list or union, it gets the flags +for that type above, otherwise it gets that flags of the Real +type above. + +More flags need to be added in order to allow more control over the +answer checkers to give the full flexibility of the traditional +WeBWorK answer checkers. Note that some things, like whether trig +functions are allowed in the answer, are controlled through the +Context() rather than the answer checker itself. For example, + + Context()->functions->undefine('sin','cos','tan'); + +would remove those three functions from use. (One would need to remove +cot, sec, csc, arcsin, asin, etc., to do this properly; there could be +a function call to do this.) + +Similarly, which arithmetic operations are available is controlled +through Context()->operations. + +The tolerances used in comparing numbers are part of the Context as +well. You can set these via: + + Context()->flags->set( + tolerance => .0001, # the relative or absolute tolerance + tolType => 'relative', # or 'absolute' + zeroLevel => 1E-14, # when to use zeroLevelTol + zeroLevelTol => 1E-12, # smaller than this matches zero + # when one of the two is less + # than zeroLevel + limits => [-2,2], # limits for variables in formulas + num_points => 5, # the number of test points + ); + +[These need to be handled better.] + +Note that for testing formulas, you can override the limits and +num_points settings by setting these fields of the formula itself: + + $f = Formula("sqrt(x-10)"); + $f->{limits} = [10,12]; + + $f = Formula("log(xy)"); + $f->{limits} = [[.1,2],[.1,2]]; # x and y limits + +You can also specify the test points explicitly: + + $f = Formula("sqrt(x-10)"); + $f->{test_points} = [[11],[11.5],[12]]; + + $f = Formula("log(xy)"); + $f->{test_points} = [[.1,.1],[.1,.5],[.1,.75], + [.5,.1],[.5,.5],[.5,.75]]; + +[There still needs to be a means of handling the tolerances similarly, +and through the ->cmp() call itself.] + --- /dev/null +++ doc/MathObjects/extensions/2-function.pg @@ -0,0 +1,83 @@ +########################################################## +# +# Example showing how to add a new two-variable function to the Parser +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################### +# +# Use standard numeric mode +# +Context('Numeric'); + +############################################# +# +# Create a "Combinations" function +# + +package MyFunction2; +our @ISA = qw(Parser::Function::numeric2); # this is what makes it R^2 -> R + +sub C { + shift; my ($n,$r) = @_; my $C = 1; + $r = $n-$r if ($r > $n-$r); # find the smaller of the two + for (1..$r) {$C = $C*($n-$_+1)/$_} + return $C +} + +package main; + +# +# Make it work on formulas as well as numbers +# +sub C {Parser::Function->call('C',@_)} + +# +# Add the new functions into the Parser +# + +Context()->functions->add(C => {class => 'MyFunction2'}); + +$x = Formula('x'); + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we have added a new function to the Parser: ${BTT}C(n,r)${ETT}. +(Edit the code to see how this is done). +$PAR +Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: +$PAR + +\{ParserTable( + 'Formula("C(x,3)")', + 'C(6,2)', + 'C($x,3)', + 'Formula("C(x,3)")->eval(x=>6)', + '(C($x,2))->eval(x=>6)', + 'Formula("C(x)")', + 'Formula("C(1,2,3)")', + 'C(1)', + 'C(1,2,3)', + )\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/4-list.pg @@ -0,0 +1,106 @@ +########################################################## +# +# Example showing how to add a new list-type object +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################## +# +# Define our own [n,r] notation for n choose r +# + +package MyChoose; +our @ISA = qw(Parser::List); # subclass of List + +# +# Check that two numbers are given +# +sub _check { + my $self = shift; + $self->{type}{list} = 0; # our result is a single number, not really a list + $self->Error("You need two numbers within '[' and ']'") + if ($self->{type}{length} < 2); + $self->Error("Only two numbers can appear within '[' and ']'") + if ($self->{type}{length} > 2); + my ($n,$r) = @{$self->{coords}}; + $self->Error("The arguments for '[n,r]' must be numbers") + unless ($n->type eq 'Number' && $r->type eq 'Number'); + $self->{type} = $Value::Type{number}; +} + +# +# Compute n choose r +# +sub _eval { + shift; my ($n,$r) = @_; my $C = 1; + $r = $n-$r if ($r > $n-$r); # find the smaller of the two + for (1..$r) {$C = $C*($n-$_+1)/$_} + return $C +} + +# +# Non-standard TeX output +# +sub TeX { + my $self = shift; + return '{'.$self->{coords}[0]->TeX.' \choose '.$self->{coords}[1]->TeX.'}'; +} + +# +# Non-standard perl output +# +sub perl { + my $self = shift; + return '(MyChoose->_eval('.$self->{coords}[0]->perl.','.$self->{coords}[1]->perl.'))'; +} + + +package main; + +########################################################## +# +# Add the new list to the context +# + +Context()->lists->add(Choose => {class => 'MyChoose'}); +Context()->parens->replace('[' => {close => ']', type => 'Choose'}); + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we have added a new list to the Parser: ${BTT}[n,r]${ETT}, +which returns \(n\choose r\). +$PAR + +\{ParserTable( + 'Formula("[x,3]")', + 'Formula("[5,3]")', + 'Formula("[x,3]")->eval(x=>5)', + '$C = Formula("[x,y]"); $C->substitute(x=>5)', + 'Formula("[x,y]")->perlFunction("C"); C(5,3)', + 'Formula("[x,y,3]")', + 'Formula("[x]")', + 'Formula("[x,[y,2]]")', + 'Formula("[x,<1,2>]")', + )\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/7-context.pg @@ -0,0 +1,86 @@ +########################################################## +# +# Example showing how to switch different contexts +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we compare formulas in complex and vector contexts. +Note the difference between how ${BTT}i${ETT} is treated in the two +contexts. Note that 'Number' comprises both real and complex numbers. +$PAR + +Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: +$PAR + +END_TEXT + +$x = Formula('x'); + +########################################################## +# +# Use Complex context +# + +Context('Complex'); + +BEGIN_TEXT +\{Title("The Complex context:")\} +$PAR +\{ParserTable( + 'i', + 'Formula("1+3i")', + 'Formula("x+3i")', + '1 + 3*i', + '$x + 3*i', + '$z = tan(2*i)', + 'Formula("sinh(zi)")', + 'Formula("3i+4j-k")', + 'Formula("3i+4j-k")->eval', + '3*i + 4*j - k', +)\} +$PAR$BR +END_TEXT + + +########################################################## +# +# Use Vector context +# + +Context('Vector'); + +BEGIN_TEXT +\{Title("The Vector context:")\} +$PAR +\{ParserTable( + 'i', + 'Formula("1+3i")', + 'Formula("x+3i")', + '1 + 3*i', + '$x + 3*i', + '$z = tan(2*i)', + 'Formula("sinh(zi)")', + 'Formula("3i+4j-k")', + 'Formula("3i+4j-k")->eval', + '3*i + 4*j - k', +)\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/5-operator.pg @@ -0,0 +1,89 @@ +########################################################## +# +# Example of how to implement equalities in the Parser +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################## +# +# Define our own operator for equality +# + +package Equality; +our @ISA = qw(Parser::BOP); # subclass of Binary OPerator + +# +# Check that the operand types are numbers. +# +sub _check { + my $self = shift; my $name = $self->{bop}; + $self->Error("Only one equality is allowed in an equation") + if ($self->{lop}->class eq 'Equality' || $self->{rop}->class eq 'Equality') ; + $self->Error("Operands of '$name' must be Numbers") unless $self->checkNumbers(); + $self->{type} = Value::Type('Equality',1); # Make it not a number, to get errors with other operations. +} + +# +# Determine if the two sides are equal +# +sub _eval {return ($_[1] == $_[2])? 1: 0} + +package main; + +# +# Add the operator into the current context +# + +$prec = Context()->operators->get(',')->{precedence} + .25; + +Context()->operators->add( + '=' => { + class => 'Equality', + precedence => $prec, # just above comma + associativity => 'left', # computed left to right + type => 'bin', # binary operator + string => '=', # output string for it + perl => '==', # perl string + } +); + + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we have added a new operator to the Parser: ${BTT} a += b${ETT}, for equality. +$PAR + +\{ParserTable( + 'Formula("x + y = 0")', + 'Formula("x + y = 0")->{tree}->class', + 'Formula("x + y = 0")->{tree}{lop}', + 'Formula("x + y = 0")->{tree}{rop}', + 'Formula("x + y = 0")->eval(x=>2,y=>3)', + 'Formula("x + y = 0")->eval(x=>2,y=>-2)', + 'Formula("x + y = 0 = z")', + 'Formula("(x + y = 0) + 5")', + 'Formula("x + y = 0, 3x-y = 4")', # you CAN get a list of equalities + )\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/8-answer.pg @@ -0,0 +1,112 @@ +########################################################## +# +# Example showing an answer checker that uses the parser +# to evaluate the student (and professor's) answers. +# +# This is now obsolete, as the paser's ->cmp method +# can be used to produce an answer checker for any +# of the parser types. +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserUtils.pl", +); + +TEXT(beginproblem()); + +########################################################## +# +# Use Vector context +# + +Context('Vector'); + +########################################################## +# +# Make the answer checker +# +sub vector_cmp { + my $v = shift; + die "vector_cmp requires a vector argument" unless defined $v; + my $v = Vector($v); # covert to vector if it isn't already + my $ans = new AnswerEvaluator; + $ans->ans_hash(type => "vector",correct_ans => $v->string, vector=>$v); + $ans->install_evaluator(~~&vector_cmp_check); + return $ans; +} + +sub vector_cmp_check { + my $ans = shift; my $v = $ans->{vector}, + $ans->score(0); # assume failure + my $f = Parser::Formula($ans->{student_ans}); + my $V = Parser::Evaluate($f); + if (defined $V) { + $V = Formula($V) unless Value::isValue($V); # make sure we can call Value methods + $ans->{preview_latex_string} = $f->TeX; + $ans->{preview_text_string} = $f->string; + $ans->{student_ans} = $V->string; + if ($V->type eq 'Vector') { + $ans->score(1) if ($V == $v); # Let the overloaded == do the check + } else { + $ans->{ans_message} = $ans->{error_message} = + "Your answer doesn't seem to be a Vector (it looks like ".Value::showClass($V).")" + unless $inputs_ref->{previewAnswers}; + } + } else { + # + # Student answer evaluation failed. + # Report the error, with formatting, if possible. + # + my $context = Context(); + my $message = $context->{error}{message}; + if ($context->{error}{pos}) { + my $string = $context->{error}{string}; + my ($s,$e) = @{$context->{error}{pos}}; + $message =~ s/; see.*//; # remove the position from the message + $ans->{student_ans} = protectHTML(substr($string,0,$s)) . + '<SPAN CLASS="parsehilight">' . + protectHTML(substr($string,$s,$e-$s)) . + '</SPAN>' . + protectHTML(substr($string,$e)); + } + $ans->{ans_message} = $ans->{error_message} = $message; + } + return $ans; +} + +########################################################## +# +# The problem text +# + +$V = Vector(1,2,3); + +Context()->flags->set(ijk=>0); +Context()->constants->add(a=>1,b=>1,c=>1); + +$ABC = Formula("<a,b,c>"); + +BEGIN_TEXT +Enter the vector \(\{$V->TeX\}\) in any way you like: \{ans_rule(20)\}. +$PAR +You can use either \(\{$ABC->TeX\}\) or \(\{$ABC->ijk\}\) notation,$BR +and can perform vector operations to produce your answer. +$PAR +${BBOLD}Note:${EBOLD} This problem is obsolete. +END_TEXT + +########################################################### +# +# The answer +# + +ANS(vector_cmp($V)); + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/6-precedence.pg @@ -0,0 +1,84 @@ +########################################################## +# +# Example of the non-standard precedences as a possible alternative +# that makes it possible to write "sin 2x" and get "sin(2x)" +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################## +# +# Use standard precedences for multiplication +# + +Context()->usePrecedence("Standard"); + +$standard = ParserTable( + 'Formula("sin 2xy/3")', + 'Formula("sin 2x y/3")', + 'Formula("sin 2x y / 3")', + 'Formula("sin 2x+5")', + 'Formula("sin x(x+1)")', + 'Formula("sin x (x+1)")', + 'Formula("1/2xy")', + 'Formula("1/2 xy")', + 'Formula("1/2x y")', + 'Formula("sin^2 x")', + 'Formula("sin^(-1) x")', + 'Formula("x^2x")', +); + +Context()->usePrecedence("Non-Standard"); + +$nonstandard = ParserTable( + 'Formula("sin 2xy/3")', + 'Formula("sin 2x y/3")', + 'Formula("sin 2x y / 3")', + 'Formula("sin 2x+5")', + 'Formula("sin x(x+1)")', + 'Formula("sin x (x+1)")', + 'Formula("1/2xy")', + 'Formula("1/2 xy")', + 'Formula("1/2x y")', + 'Formula("sin^2 x")', + 'Formula("sin^(-1) x")', + 'Formula("x^2x")', +); + + + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we compare the standard and non-standard precedences for +multiplication. +$PAR + +\{Title("The Non-Standard precedences:")\} +$PAR +$nonstandard +$PAR$BR + +\{Title("The Standard precedences:")\} +$PAR +$standard + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/3-operator.pg @@ -0,0 +1,113 @@ +########################################################## +# +# Example showing how to add new operators to the Parser +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################## +# +# Define our own binary operator +# + +package MyOperator; +our @ISA = qw(Parser::BOP); # subclass of Binary OPerator + +# +# Check that the operand types are numbers. +# +sub _check { + my $self = shift; my $name = $self->{bop}; + return if $self->checkNumbers(); + $self->Error("Operands of '$name' must be Numbers"); +} + +# +# Compute the value of n choose r. +# +sub _eval { + shift; my ($n,$r) = @_; my $C = 1; + $r = $n-$r if ($r > $n-$r); # find the smaller of the two + for (1..$r) {$C = $C*($n-$_+1)/$_} + return $C +} + +# +# Non-standard TeX output +# +sub TeX { + my $self = shift; + return '{'.$self->{lop}->TeX.' \choose '.$self->{rop}->TeX.'}'; +} + +# +# Non-standard perl output +# +sub perl { + my $self = shift; + return '(MyOperator->_eval('.$self->{lop}->perl.','.$self->{rop}->perl.'))'; +} + +package main; + +########################################################## +# +# Add the operator into the current context +# + +$prec = Context()->operators->get('+')->{precedence} - .25; + +Context()->operators->add( + '#' => { + class => 'MyOperator', + precedence => $prec, # just below addition + associativity => 'left', # computed left to right + type => 'bin', # binary operator + string => ' # ', # output string for it + TeX => '\mathop{\#}', # TeX version (overridden above, but just an example) + } +); + + +$CHOOSE = MODES(TeX => '\#', HTML => '#'); + + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we have added a new operator to the Parser: ${BTT}n $CHOOSE r${ETT}, +which returns \(n\choose r\). +$PAR + +\{ParserTable( + 'Formula("x # y")', + 'Formula("x+1 # 5")', + 'Formula("x # 5")->eval(x=>7)', + 'Formula("(x#5)+(x#4)")', + 'Formula("x#5+x#4")', + 'Formula("x # y")', + 'Formula("x # y")->substitute(x=>5)', + 'Formula("x # y")->eval(x=>5,y=>2)', + 'Formula("x # y")->perlFunction(~~'C~~'); C(5,2)', + 'Formula("1 # <x,3>")', + )\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/extensions/1-function.pg @@ -0,0 +1,83 @@ +########################################################## +# +# Example showing how to add a new single-variable function to the Parser +# + +DOCUMENT(); # This should be the first executable line in the problem. + +loadMacros( + "PGbasicmacros.pl", + "PGanswermacros.pl", + "Parser.pl", + "parserTables.pl", +); + +TEXT(beginproblem()); + +########################################################### +# +# Use standard numeric mode +# +Context('Numeric'); + +############################################# +# +# Create a 'log2' function to the Parser for log base 2 +# + +package MyFunction1; +our @ISA = qw(Parser::Function::numeric); # this is what makes it R -> R + +sub log2 { + shift; my $x = shift; + return CORE::log($x)/CORE::log(2); +} + +package main; + +# +# Make it work on formulas as well as numbers +# +sub log2 {Parser::Function->call('log2',@_)} + +# +# Add the new functions into the Parser +# + +Context()->functions->add( + log2 => {class => 'MyFunction1', TeX => '\log_2'}, # fancier TeX output +); + +$x = Formula('x'); + +########################################################### +# +# The problem text +# +BEGIN_TEXT +$BEGIN_ONE_COLUMN + +In this problem, we have added a new function to the Parser: ${BTT}log2(x)${ETT}. +(Edit the code to see how this is done.) +$PAR +Assuming that ${BTT}${DOLLAR}x = Formula('x')${ETT}, it can be used as follows: +$PAR + +\{ParserTable( + 'Formula("log2(x)")', + 'log2(8)', + 'log2($x+1)', + 'Formula("log2(x)")->eval(x=>16)', + '(log2($x))->eval(x=>16)', + 'Formula("log2()")', + 'Formula("log2(1,x)")', + 'log2()', + 'log2(1,3)', + )\} + +$END_ONE_COLUMN +END_TEXT + +########################################################### + +ENDDOCUMENT(); # This should be the last executable line in the problem. --- /dev/null +++ doc/MathObjects/macros/parserTables.pl @@ -0,0 +1,87 @@ +loadMacros("parserUtils.pl"); + +############################################# +# +# For Parser example tables: +# + +$BTT = MODES(TeX=>'{\tt ', Latex2HTML => $bHTML.'<TT>'.$eHTML, HTML => '<TT>'); +$ETT = MODES(TeX=>'}', Latex2HTML => $bHTML.'</TT>'.$eHTML, HTML => '</TT>'); + +$BC = MODES( + TeX=>'{\small\it ', + Latex2HTML => $bHTML.'<SMALL><I>'.$eHTML, + HTML => '<SMALL><I>' +); +$EC = MODES( + TeX=>'}', + Latex2HTML => $bHTML.'</I></SMALL>'.$eHTML, + HTML => '</I></SMALL>' +); + +$LT = MODES(TeX => "<", Latex2HTML => "<", HTML => '<'); +$GT = MODES(TeX => ">", Latex2HTML => ">", HTML => '>'); + +$TEX = MODES(TeX => '{\TeX}', HTML => 'TeX', HTML_dpng => '\(\bf\TeX\)'); + +@rowOptions = ( + indent => 0, + separation => 0, + align => 'LEFT" NOWRAP="1', # alignment hack to get NOWRAP +); + +sub ParserRow { + my $f = shift; my $t = ''; + Context()->clearError; + my ($s,$err) = PG_restricted_eval($f); + if (defined $s) { + my $ss = $s; + if (ref($s) && \&{$s->string}) { + $t = '\('.$s->TeX.'\)'; + $s = $s->string; + } elsif ($s !~ m/^[a-z]+$/i) { + $t = '\('.Formula($s)->TeX.'\)'; + $s = Formula($s)->string; + } + $s =~ s/</$LT/g; $s =~ s/>/$GT/g; + if (ref($ss) && \&{$ss->class}) { + if ($ss->class eq 'Formula') { + $s .= ' '.$BC.'(Formula returning '.$ss->showType.')'.$EC; + } else { + $s .= ' '.$BC.'('.$ss->class.' object)'.$EC; + } + } + } else { + $s = $BC. (Context()->{error}{message} || $err) . $EC; + $t = ''; + } + $f =~ s/</$LT/g; $f =~ s/>/$GT/g; + if ($displayMode eq 'TeX') { + $f =~ s/\^/\\char`\\^/g; $s =~ s/\^/\\char`\\^/g; + $f =~ s/#/\\#/g; $s =~ s/#/\\#/g; + } + my $row = Row([$BTT.$f.$ETT,$BTT.$s.$ETT,$t],@rowOptions); + $row =~ s/\$/\${DOLLAR}/g; + return $row; +} + +sub ParserTable { + my $table = + BeginTable(border=>1, padding=>20). + Row([$BBOLD."Perl Code".$EBOLD, + $BBOLD."Result".$EBOLD, + $BBOLD.$TEX.' version'.$EBOLD],@rowOptions); + foreach my $f (@_) {$table .= ParserRow($f)} + $table .= EndTable(); + return $table; +} + +sub Title { + my $title = shift; + + MODES( + TeX => "\\par\\centerline{\\bf $title}\\par\\nobreak\n", + Latex2HTML => $bHTML.'<CENTER><H2>'.$title.'</H2></CENTER>'.$eHTML, + HTML => '<CENTER><H2>'.$title.'</H2></CENTER>' + ); +} --- /dev/null +++ doc/MathObjects/macros/Differentiation.pl @@ -0,0 +1,20 @@ +# +# Example of how to add new functionality to the Parser. +# +# Here we load new methods for the Parser object classes. Note, however, +# that these are PERSISTANT when used with webwork2 (mod_perl), and so we +# need to take care not to load them more than once. We look for the +# variable $Parser::Differentiation::loaded, which is defined in the +# differentiation package, in order to tell. +# +# DifferentiationDefs.pl is really just a copy of the +# Parser::Differentiation.pm file, and you really could just preload the +# latter instead by uncommenting the 'use Parser::Differentiation' line at +# the bottom of Parser.pm. (This file is really just a sample). The way +# it's done here will load it the first time it gets used, then will keep +# it around, so not much overhead even this way. +# + +loadMacros("DifferentiationDefs.pl") unless $Parser::Differentiation::loaded; + +1; --- /dev/null +++ doc/MathObjects/macros/DifferentiationDefs.pl @@ -0,0 +1,635 @@ +# +# Extend differentiation to multiple variables +# Check differentiation for complex functions +# Do derivatives for norm and unit. +# +# Make shortcuts for getting numbers 1, 2, and sqrt, etc. +# + +################################################## +# +# Differentiate the formula in terms of the given variable +# +sub Parser::D { + my $self = shift; my $x = shift; + if (!defined($x)) { + my @vars = keys(%{$self->{variables}}); + my $n = scalar(@vars); + if ($n == 0) { + return $self->new('0') if $self->{isNumber}; + $x = 'x'; + } else { + $self->Error("You must specify a variable to differentiate by") unless $n ==1; + $x = $vars[0]; + } + } else { + return $self->new('0') unless defined $self->{variables}{$x}; + } + return $self->new($self->{tree}->D($x)); +} + +sub Item::D { + my $self = shift; + my $type = ref($self); $type =~ s/.*:://; + $self->Error("Differentiation for '$type' is not implemented",$self->{ref}); +} + + +######################################################################### + +sub Parser::BOP::comma::D {Item::D(shift)} +sub Parser::BOP::union::D {Item::D(shift)} + +sub Parser::BOP::add::D { + my $self = shift; my $x = shift; + $self = Parser::BOP->new( + $self->{equation},$self->{bop}, + $self->{lop}->D($x),$self->{rop}->D($x) + ); + return $self->reduce; +} + + +sub Parser::BOP::subtract::D { + my $self = shift; my $x = shift; + $self = Parser::BOP->new( + $self->{equation},$self->{bop}, + $self->{lop}->D($x),$self->{rop}->D($x) + ); + return $self->reduce; +} + +sub Parser::BOP::multiply::D { + my $self = shift; my $x = shift; + my $equation = $self->{equation}; + $self = + Parser::BOP->new($equation,'+', + Parser::BOP->new($equation,$self->{bop}, + $self->{lop}->D($x),$self->{rop}->copy($equation)), + Parser::BOP->new($equation,$self->{bop}, + $self->{lop}->copy($equation),$self->{rop}->D($x)) + ); + return $self->reduce; +} + +sub Parser::BOP::divide::D { + my $self = shift; my $x = shift; + my $equation = $self->{equation}; + $self = + Parser::BOP->new($equation,$self->{bop}, + Parser::BOP->new($equation,'-', + Parser::BOP->new($equation,'*', + $self->{lop}->D($x),$self->{rop}->copy($equation)), + Parser::BOP->new($equation,'*', + $self->{lop}->copy($equation),$self->{rop}->D($x)) + ), + Parser::BOP->new($equation,'^', + $self->{rop},Parser::Number->new($equation,2) + ) + ); + return $self->reduce; +} + +sub Parser::BOP::power::D { + my $self = shift; my $x = shift; + my $equation = $self->{equation}; + my $vars = $self->{rop}->getVariables; + if (defined($vars->{$x})) { + $vars = $self->{lop}->getVariables; + if (defined($vars->{$x})) { + $self = + Parser::Function->new($equation,'exp', + [Parser::BOP->new($equation,'*',$self->{rop}->copy($equation), + Parser::Function->new($equation,'log',[$self->{lop}->copy($equation)],0))]); + return $self->D($x); + } + $self = Parser::BOP->new($equation,'*', + Parser::Function->new($equation,'log',[$self->{lop}->copy($equation)],0), + Parser::BOP->new($equation,'*', + $self->copy($equation),$self->{rop}->D($x)) + ); + } else { + $self = + Parser::BOP->new($equation,'*', + Parser::BOP->new($equation,'*', + $self->{rop}->copy($equation), + Parser::BOP->new($equation,$self->{bop}, + $self->{lop}->copy($equation), + Parser::BOP->new($equation,'-', + $self->{rop}->copy($equation), + Parser::Number->new($equation,1) + ) + ) + ), + $self->{lop}->D($x) + ); + } + return $self->reduce; +} + +sub Parser::BOP::cross::D {Item::D(shift)} +sub Parser::BOP::dot::D {Item::D(shift)} +sub Parser::BOP::underscore::D {Item::D(shift)} + +######################################################################### + +sub Parser::UOP::plus::D { + my $self = shift; my $x = shift; + return $self->{op}->D($x) +} + +sub Parser::UOP::minus::D { + my $self = shift; my $x = shift; + $self = Parser::UOP->new($self->{equation},'u-',$self->{op}->D($x)); + return $self->reduce; +} + +sub Parser::UOP::factorial::D {Item::D(shift)} + +######################################################################### + +sub Parser::Function::D { + my $self = shift; + $self->Error("Differentiation of '$self->{name}' not implemented",$self->{ref}); +} + +sub Parser::Function::D_chain { + my $self = shift; my $x = $self->{params}[0]; + my $name = "D_" . $self->{name}; + $self = Parser::BOP->new($self->{equation},'*',$self->$name($x->copy),$x->D(shift)); + return $self->reduce; +} + +############################# + +sub Parser::Function::trig::D {Parser::Function::D_chain(@_)} + +sub Parser::Function::trig::D_sin { + my $self = s... [truncated message content] |