Introduction to Contexts
Contents
What is a Context?
Every webwork problem has a collection of variables and constants that are part of the problem, and a setting in which these reside. For example, some problems use vector-valued formulas, and others might be about intervals on the real line. Some questions allow students to perform numerical calculations as part of their answers, while others require that they simplify their answers to a single number before entering it. In some questions a less-than and greater-than signs might be used to describe intervals (e.g., x < 5
) while in others they are used to delimit vectors (as in <3,-1,2>
). Similarly, in a problem on vectors, the letter i
might represent the coordinate unit vector along the [math]x[/math]-axis, while in a question on complex numbers, i
might represent the complex number [math]\sqrt{-1}[/math].
All of these features represent the context of the problem, and MathObjects maintains this information in a special Context object. The Context object (or simply Context) holds information about what variables are defined and their types and domains, what constants are available, what functions can be applied and what operations performed, what words (like NONE
and DNE
) are available, and what interpretation to give to the various symbols and delimiters that the student can type.
There are predefined contexts for working with things like real numbers, complex numbers, intervals, vectors, matrices, and so on. There are also a large number of extensions that implement specialized Contexts for particular purposes: scientific notation, currency values, inequalities, fractions, polynomials, chemical reactions, and so forth. It is possible to modify an existing Context to suit the needs of your problems, and you can even create your own custom Contexts (though this is an advanced topic).
Selecting a Context
Each Context has a name, and you select a context by adding the command
Context("name");
at the top of a problem file, where name
is the name of the desired Context. The pre-defined contexts are listed on the Common Contexts page, and these include Numeric
, Complex
, Vector
, Matrix
, and Interval
.
There are a number of Contexts implemented as extensions to the MathObjects library. These are in the pg/macros/
directory; files whose names begin with context
implement specialized Contexts. Most of these are listed in the Specialized contexts documentation. In order to use one of these additional Contexts, include the macro file for that Context in the loadMacros()
call at the top of your file. For example:
loadMacros( "PGstandard.pl", "MathObjects.pl", "contextLimitedPolynomial.pl", "PGcourse.pl", );
loads the contextLimitedPolynomial.pl
macro file, which implements the LimitedPolynomial
Context, which requires students to specify only "simplified" polynomials in the form [math]a_n x^n + ... + a_0 [/math] as their answers. Inspecting files like contextLimitedPolynomial.pl
is one way to learn how to modify a context to meet your requirements, though that is an advanced topic.
Changing Context Settings
Since the Context stores information about things like the variables and constants for the problem, you need a way to specify what these are. You do this by using methods of the Context object. In order to obtain a reference to the current Context, use the Context()
function with no context name; this returns the context that is active.
$context = Context();
The Context give you access to the various types of information that it stores via methods for each type, such as Context()->variables
is a reference to an object that holds the information about the variables in the Context, while Context()->constants
is a reference to the object that manages the constants in the Context. Each of these in turn has methods for adding, modifying, removing, and listing the values that they store. For example,
Context()->variables->add(t => 'Real');
adds a new variable t
that is a Real value.
All the types of data listed below have the following methods:
Method | Description |
---|---|
add(name => data)
|
for adding new items |
are(name => data)
|
for removing all current items and adding new ones |
set(name => data)
|
for modifying existing items |
get(name)
|
for getting the data defining a particular item |
replace(name => data)
|
for replacing an existing definition by a new one |
undefine(name)
|
for making an item be undefined, but still recognized |
redefine(name)
|
for making the item be defined again |
remove(name)
|
for deleting a particular item |
clear
|
for removing all current definitions |
names
|
for retrieving the names of all the defined items |
all
|
for retrieving the entire collection of data for all items |
These are explained in more detail in the Modifying contexts (advanced) documentation. Several of these are used in the examples below, and can serve as an introduction to their use.
In addition to direct calls to the data objects, it is possible to modify the Context in other ways. For example, some function calls modify the Context, e.g.,
Parser::Number::NoDecimals(Context());
Also, some macro files provide mechanisms for changing the context, as in the following,
loadMacros("contextLimitedPowers.pl"); Context()->operators->set(@LimitedPowers::OnlyIntegers);
which sets the operators to limit exponents to only integer values.
Variables
Each Context comes with one or more pre-defined variables. For example the Numeric
Context includes the variable [math]x[/math], while the Complex
Context has the variable [math]z[/math]. The Point
and Vector
Contexts have variables [math]x[/math], [math]y[/math] and [math]z[/math].
To add a new variable to a context, use
Context()->variables->add(name => type);
where name
is the name of the variable to be added, and type
is the kind of MathObject that is to be stored. The valid types are "Real"
, "Complex"
, "Point2D"
, "Point3D"
, "Vector2D"
, "Vector3D"
, "Parameter"
, or an instance of a MathObject of the appropriate type. You can also add multiple variables at once. For example,
Context()->variables->add(y => "Real"); Context()->variables->add(v => Vector(1,2,3,4)); Context()->variables->add(u => "Complex", w => "Complex");
adds a real variable named y
, a variable v
holding a four-dimensional vector, and two complex variables, u
and w
.
You can specify the domain of a variable using the set()
method, as in the following,
Context()->variables->set(x => {limits => [3,5]});
which sets the limits for [math]x[/math] to be from 3 to 5. Again, multiple variables can be set at once.
You can set both the type and the limits at once when you add the variable, as in
Context()->variables->add(t => ["Real",limits => [0,1]]);
If the type of a variable is "Parameter"
, then this variable is used as an adaptive parameter, meaning WeBWorK will try to pick a value of the variable so that the student's answer matches the professor's answer with that value set. Note that student's can't enter adaptive parameters of their own; only the correct answer can include them. Also note that adaptive parameters only work in a linear setting, e.g., [math]af+b[/math], where [math]a[/math] and [math]b[/math] are the adaptive parameters, and [math]f[/math] is the basic correct formula. That is, you can adapt for linear multiples, and constant addition. Once the formulas have been compared, the adapted value for the parameter can be obtained using the value()
method described below.
A variable can be removed via the remove()
method:
Context()->variables->remove("t");
Finally, the names of all the variables in the Context can be displayed by placing
TEXT(join(',',Context()->variables->names));
into the body of a PG problem.
In addition to the methods shown above, the variables
object has the following methods:
Method | Description |
---|---|
Context()->variables->type("name")
|
Returns the type of the variable (this is a typeRef hash; see reference needed). |
Context()->variables->value("name")
|
Returns the value of an adaptive parameter, once it has been used in a Formula comparison. |
Context()->variables->parameters
|
Returns an array of the names of all the adaptive parameters in the Context. |
Context()->variables->variables
|
Returns an array of the names of all the variables (i.e., non-adaptive-parameters) in the Context. |
Constants
The values of variables like [math]\pi[/math] and [math]e[/math] are stored in the constants
data of the Context. The values of pi
and e
are defined in all the main Contexts. The Vector
and Matrix
Contexts include i
, j
, and k
, which represent the coordinate unit vectors, while the Complex
Context has i
defined as the imaginary number [math]i = \sqrt{-1}[/math]. The Interval
context has a constant R
which is equal to the whole real line -- the interval (-infinity,infinity)
.
Constants are added to the Context via the add()
method. The data is the value for the constant, and you can set several constants at once. For example:
Context()->constants->add(tau => Real(pi*2)); Context()->constants->add(n => Real(3), m => Real(2));
Usually when a Formula containing a constant is parsed, the name of the constant is retained, so Formula("sin(pi)")
would remain as sin(pi)
rather than 0
. You can use the set()
method to set the keepName
parameter for a constant to change this so that the value is substituted into the Formula when it is used. E.g.,
Context()->constants->set(pi => {keepName => 0}); $f = Formula("sin(pi)");
would be the same as having set $f = Formula("0")
.
A constant can be removed via the remove()
method:
Context()->constants->remove("pi");
Finally, the names of all the constant in the Context can be displayed by placing
TEXT(join(',',Context()->constants->names));
into the body of a PG problem.
In addition to the methods shown above, the constants
object has the following methods:
Method | Description |
---|---|
Context()->variables->value("name")
|
Returns the value of the constant. |
Strings
Most Contexts include some special words that the students can type, like NONE
or DNE
(for "does not exist"). These can be used in problems that ask for lists that might be empty (where NONE
can be used in that case), or for limit problems where the limit might not exist. These two words are available in all the pre-defined contexts, and also the word infinity
, which generates the Infinity object, with inf
as an alias for the complete word. In general, words like these are not case-sensitive, so INF
would produce infinity
, as would InfINIty
or any other capitalization of the word.
You can add your own words to the Context via the add()
method. Usually, there is no data, so you simply use {}
as the definition. To make a string case sensitive, use {caseSensitive =>1}
as the data. To make a word create infinity, use {infinite => 1}
. To make one word mean the same as another, use {alias => "name"}
. For example,
Context()->strings->add(A => {}, B => {}, C => {}, D => {}); Context()->strings->add(True => {}, False => {}, T => {alias => "True"}, F => {alias => "False"}); Context()->strings->add(WeBWorK => {caseSensitive => 1}); Context()->strings->add(unbounded => {infinite => 1});
Here, A
, B
, C
, and D
are now valid answers for a student to type, but they could also be a
, b
, c
, or d
. True
and False
(with any capitalization) are now available as well, along with T
as an alternative to True
and F
as an alternative for False
. The word WeBWorK
can be entered, but only with this capitalization (as it should be). Finally, unbounded
will produce the positive Infinity object (so -unbounded
would be the negative Infinity object).
A string can be removed via the remove()
method:
Context()->strings->remove("DNE");
Finally, the names of all the constant in the Context can be displayed by placing
TEXT(join(',',Context()->strings->names));
into the body of a PG problem.
Flags
Many settings for the Context are stored as flags, which are parameters that control special functions of the parsing process, the answer checkers, or other features of the MathObjects library. For example, the default tolerance for numeric comparisons is stored as a flag, as are the default limits for variable ranges. Other values include things like whether to display Vectors using [math]ijk[/math]-notation rather than coordinate form with angle-bracket delimiters, and how to show student answers in the "Entered" and "Preview" columns of the results table when they submit their answers.
You do not usually add new flags (though you could if you wanted to keep track of information for your own custom MathObject classes). The most common actions are to set or get the values of the flags. To set a flag, use the set()
with the data being the name of the flag to set and its new value. As with the previous case, you can set several flags at once.
Context()->flags->set(tolerance => .005); Context()->flags->set( reduceConstants => 0, reduceConstantFunctions => 0, ); Context()->flags->set(formatStudentAnswers=>"parsed");
To get the value of a flag, you could use the get
method, but since this is such a common operation, there is a shorthand. For example,
$tol = Context()->flag("tolerance");
gets the current tolerance value from the Context.
Note that MathObject and answer checkers often can override the settings of the Context itself. That means you may need to check the properties of an MathObject and the properties of the active answer checker (if there is one) before resorting to the Context's value. MathObjects gives you an easy way to do that, however, with the getFlag()
method of a MathObject. For example, if you are writing a custom answer checker, you could use
ANS($mo->cmp(checker => sub { my ($correct,$student,$ansHash) = @_; my $tol = $correct->getFlag("tolerance",.001); ... (use $tol here) ... return $score; }));
to obtain the value of the tolerance
. This will be taken from $correct
if it was set there, otherwise from the current answer checker's flags as passed to $mo->cmp()
, and then from the Context for $mo
. If the flag is not set in any of those, the value .001
is used.
The names of all the constants in the Context can be displayed by placing
TEXT(join(',',Context()->flags->names));
into the body of a PG problem. Note that different contexts may have different flags, and that some flags that could be set may not currently have any value in the Context. Most of the important flags are described in the Context flags documentation.
Functions
The Context includes information about the functions that are available. For example, the Complex
Context has Re()
and Im()
functions, as well as arg()
, mod()
, and conj()
, and the Vector
Context has norm()
and unit()
. These are not available in the Numeric
context.
In some problems, you may want to remove some functions so that students can't enter them. For example, if you want to have the student evaluate the sine function at a particular value, you would not want her to be able to use sin()
in her answer or that would defeat the purpose. To remove a function, use the disable()
method. For instance,
Context()->functions->disable("sin");
makes the sin()
function unavailable to the student. (Note: the function is still recognized by MathObjects, but the student will be told it is not available in this problem if it is used. The remove()
method would remove the function entirely, making it unknown to MathObjects, so the error message would be less useful to the student.)
To make the function available again, use enable
, e.g.
Context()->functions->enable("sin");
You can disable or enable more than one function at a time by listing their names separated by commas. There are also categories of functions that you can disable or enable all at once. A full list is available in Answer Checkers and the Context. Some of the common ones are Trig
to disable all trigonometric functions, Numeric
to disable things like ln()
and sqrt()
, Complex
to disable the complex functions, and All
to disable all the functions. For example,
Context()->functions->disable("All"); Context()->functions->enable("sqrt");
would disable all functions and allow only the square root function.
Note that if you disable the sqrt()
function, you may want to disable the exponentiation operators so that students can't use a^(1/2)
or a**.5
to produce square roots. Similarly, if you disable abs()
, you would want to remove the absolute value vertical bars so that students can't use |a|
. See Answer Checkers and the Context for details.
It is possible to add new functions in several ways. The pg/macros/parserFunctions.pl
file implements an easy way to add functions to a Context using Formula objects. Functions written in Perl code can be added to the context using the techniques outlined in the second example on the AddingFunctions page. Alternatively, functions defined using Perl code can be added to the Context, as described in the Adding New Functions documentation.
The names of all the functions in the Context can be displayed by placing
TEXT(join(',',Context()->functions->names));
into the body of a PG problem.
In addition to the methods listed above, the functions
object has the following:
Method | Description |
---|---|
Context()->functions->disable("name")
|
Marks the named function(s) or category of functions so that they can't be used in student answers. |
Context()->functions->enable("name")
|
Re-enabled the named function(s) or category of functions so that they can be used in student answers. |
Operators
The operators that are allowed within a student's answer are controlled by the Context. All the pre-defined Contexts include the standard operators like +
and -
for addition and subtraction, *
and /
for multiplication and division, ^
or **
for exponentiation, !
for factorial, and ,
for forming lists. Some contexts include U
for taking unions, .
for taking dot products, and ><
for taking cross products.
In some problems, you may want to remove some operators so that students can't enter them. For example, if you want to have the student compute the value of an expression, you would not want her to be able to include the operations from that expression in her answer. To remove a function, use the undefine()
method. For instance,
Context()->operators->undefine("^","**");
makes exponentiation unavailable to the student. (Note: the operations still are recognized by MathObjects, but the student will be told they are not available in this problem if they are used. The remove()
method would remove the operators entirely, making them unknown to MathObjects, so the error message would be less useful to the student.)
To make the operator available again, use redefine
, e.g.
Context()->operators->redefine("^","**");
Note that multiplication and division have several forms (in order to make a non-standard precedence that allows things like sin(2x)
to be entered as sin 2x
). So if you want to disable them you need to include all of them. E.g.,
Context()->operators->undefine('*',' *','* '); Context()->operators->undefine('/',' /','/ ','//');
would be required in order to make multiplication and division unavailable.
The names of all the operators in the Context can be displayed by placing
TEXT(join(',',Context()->operators->names));
into the body of a PG problem.
The pg/macros/ directory contains a number of predefined contexts that limit the operations that can be performed in a student answer. For example, the contextLimitedNumeric.pl
file defines contexts in which students can enter numbers, but no operations, so they would have to reduce their answer to a single number by hand. There are limited contexts for complex numbers, points, and vectors, and there are also specialized contexts for entering polynomials, or where powers are restricted in various ways.
Reduction Rules
When random numbers are used as coefficients in Formulas, it is possible to get situations like 1 x^2 + -3 x + 0
, and it looks bad to have the coefficient of 1, the + -
, and the + 0
as part of the formula. For that reason, the Formula class has a reduce()
method that will normalize the Formula to remove such issues. The reduction rules are controlled through the Context's reduction
object. This lists the various reduction rules and determines which ones are active. You can turn these on and off using the set()
method, as in
Context()->reductions->set("(-x)-y" => 0);
to turn off the reduction that converts (-x)-y
to -(x+y)
. As usual, you can set the values for multiple rules in one set()
call.
Because the set()
call is rather verbose, there are shorthands for turning reduction rules on and off via the reduce()
and noreduce()
methods. For example,
Context()->noreduce("(-x)-y");
is equivalent to the set()
above. Again, you can supply multiple rules at once:
Context()->noreduce("(-x)-y","(-x)+y");
These commands set the reduction rules globally, so they affect all reductions that follow. It is also possible, however, to temporarily suspend certain reduction rules during the reduction process for a specific Formula, as in the following.
$f->reduce("(-x)-y" => 0, "(-x)+y" => 0);
which turns off the reduction rules only for this one reduction.
There are two reductions that occur during the parsing of any formula: the first is that if an operation occurs between two constants, the operation is performed and the result is put into the Formula instead; the second is that if a function call is made on a constant value, the result of the function call is used instead. For example,
$a = 2; $b = 3; $f = Formula("$a * $b * x + 4 * $a"); $g = Formula("abs(-$b)");
would produce the formula 6 * x + 8
rather than 2 * 2 * x + 4 * 2
for $f
, and 3
rather than abs(-3)
for $g
.
These operations are controlled by two Context flags (not reduction rules, since they apply to all parsing, not just reduce()
calls). These are the reduceConstants
and reduceConstantFunctions
flags. You can unset these to prevent those reductions from occurring automatically, as in
Context()->flags->set( reduceConstants => 0, reduceConstantFunctions => 0, ); $f = Formula("(1+sqrt(5))/2)");
in which case $f
would remain (1 + sqrt(5))/2
rather than the usual 1.61803
.
The names of all the reduction rules in the Context can be displayed by placing
TEXT(join(',',Context()->reductions->names));
into the body of a PG problem. There is also an annotated available on the Reduction rules for MathObject Formulas page.