Difference between revisions of "Introduction to Contexts"
PhilipLoewen (talk | contribs) m (Correct spelling of "infinite") |
|||
(27 intermediate revisions by 9 users not shown) | |||
Line 1: | Line 1: | ||
− | == Contexts == |
||
+ | == What is a Context? == |
||
− | <p style="border: 1px solid black; padding: 3px; background-color: rgb(238, 238, 238);"> The <strong>Context</strong> of a problem determines how variables are interpreted, sets the default variable(s), determines default constants available in the problem, and sets appropriate default system values such as the tolerance for student errors, etc. |
||
+ | 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., <code>x < 5</code>) while in others they are used to delimit vectors (as in <code><3,-1,2></code>). Similarly, in a problem on vectors, the letter <code>i</code> might represent the coordinate unit vector along the <math>x</math>-axis, while in a question on complex numbers, <code>i</code> might represent the complex number <math>\sqrt{-1}</math>. |
||
− | It also adjusts the error messages for student responses so that, for example, "4i +5j +6k" will |
||
− | result in a syntax error message in "Numeric" context but not in "Vector" context.</p> |
||
− | === List of Basic Contexts === |
||
+ | 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 <code>NONE</code> and <code>DNE</code>) are available, and what interpretation to give to the various symbols and delimiters that the student can type. |
||
− | Usually we select a Context by including |
||
+ | 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"); |
Context("name"); |
||
− | at the top of a problem file, where <code>name</code> is one of: |
||
+ | at the top of a problem file, where <code>name</code> is the name of the desired Context. The pre-defined contexts are listed on the [[Common Contexts]] page, and these include <code>Numeric</code>, <code>Complex</code>, <code>Vector</code>, <code>Matrix</code>, and <code>Interval</code>. |
||
+ | |||
+ | There are a number of Contexts implemented as extensions to the MathObjects library. These are in the <code>[https://github.com/openwebwork/pg/tree/master/macros pg/macros/]</code> directory; files whose names begin with <code>context</code> 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 <code>loadMacros()</code> call at the top of your file. For example: |
||
+ | |||
+ | loadMacros( |
||
+ | "PGstandard.pl", |
||
+ | "MathObjects.pl", |
||
+ | "contextLimitedPolynomial.pl", |
||
+ | "PGcourse.pl", |
||
+ | ); |
||
+ | |||
+ | loads the <code>contextLimitedPolynomial.pl</code> macro file, which implements the <code>LimitedPolynomial</code> 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 <code>contextLimitedPolynomial.pl</code> 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 <code>Context()</code> 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 <code>Context()->variables</code> is a reference to an object that holds the information about the variables in the Context, while <code>Context()->constants</code> 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 <code>t</code> that is a Real value. |
||
+ | |||
+ | All the types of data listed below have the following methods: |
||
+ | |||
+ | {| class="wikitable" |
||
+ | ! Method !! Description |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>add(name => data)</code> |
||
+ | | style="padding:5px" | for adding new items |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>are(name => data)</code> |
||
+ | | style="padding:5px" | for removing all current items and adding new ones |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>set(name => data)</code> |
||
+ | | style="padding:5px" | for modifying existing items |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>get(name)</code> |
||
+ | | style="padding:5px" | for getting the data defining a particular item |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>replace(name => data)</code> |
||
+ | | style="padding:5px" | for replacing an existing definition by a new one |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>undefine(name)</code> |
||
+ | | style="padding:5px" | for making an item be undefined, but still recognized |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>redefine(name)</code> |
||
+ | | style="padding:5px" | for making the item be defined again |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>remove(name)</code> |
||
+ | | style="padding:5px" | for deleting a particular item |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>clear</code> |
||
+ | | style="padding:5px" | for removing all current definitions |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>names</code> |
||
+ | | style="padding:5px" | for retrieving the names of all the defined items |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>all</code> |
||
+ | | style="padding:5px" | 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 <code>Numeric</code> Context includes the variable <math>x</math>, while the <code>Complex</code> Context has the variable <math>z</math>. The <code>Point</code> and <code>Vector</code> 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 <code>name</code> is the name of the variable to be added, and <code>type</code> is the kind of MathObject that is to be stored. The valid types are <code>"Real"</code>, <code>"Complex"</code>, <code>"Point2D"</code>, <code>"Point3D"</code>, <code>"Vector2D"</code>, <code>"Vector3D"</code>, <code>"Parameter"</code>, 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 <code>y</code>, a variable <code>v</code> holding a four-dimensional vector, and two complex variables, <code>u</code> and <code>w</code>. |
||
+ | |||
+ | You can specify the domain of a variable using the <code>set()</code> 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 <code>"Parameter"</code>, 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 <code>value()</code> method described below. |
||
+ | |||
+ | A variable can be removed via the <code>remove()</code> 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 <code>variables</code> object has the following methods: |
||
+ | |||
+ | {| class="wikitable" |
||
+ | ! Method !! Description |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->variables->type("name")</code> |
||
+ | | style="padding:5px" | Returns the type of the variable (this is a typeRef hash; see '''reference needed'''). |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->variables->value("name")</code> |
||
+ | | style="padding:5px" | Returns the value of an adaptive parameter, once it has been used in a Formula comparison. |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->variables->parameters</code> |
||
+ | | style="padding:5px" | Returns an array of the names of all the adaptive parameters in the Context. |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->variables->variables</code> |
||
+ | | style="padding:5px" | 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 <code>constants</code> data of the Context. The values of <code>pi</code> and <code>e</code> are defined in all the main Contexts. The <code>Vector</code> and <code>Matrix</code> Contexts include <code>i</code>, <code>j</code>, and <code>k</code>, which represent the coordinate unit vectors, while the <code>Complex</code> Context has <code>i</code> defined as the imaginary number <math>i = \sqrt{-1}</math>. The <code>Interval</code> context has a constant <code>R</code> which is equal to the whole real line -- the interval <code>(-infinity,infinity)</code>. |
||
+ | |||
+ | Constants are added to the Context via the <code>add()</code> 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 <code>Formula("sin(pi)")</code> would remain as <code>sin(pi)</code> rather than <code>0</code>. You can use the <code>set()</code> method to set the <code>keepName</code> 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 <code>$f = Formula("0")</code>. |
||
+ | |||
+ | A constant can be removed via the <code>remove()</code> 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 <code>constants</code> object has the following methods: |
||
+ | |||
+ | {| class="wikitable" |
||
+ | ! Method !! Description |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->variables->value("name")</code> |
||
+ | | style="padding:5px" | Returns the value of the constant. |
||
+ | |||
+ | |} |
||
+ | |||
+ | === Strings === |
||
+ | |||
+ | Most Contexts include some special words that the students can type, like <code>NONE</code> or <code>DNE</code> (for "does not exist"). These can be used in problems that ask for lists that might be empty (where <code>NONE</code> 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 <code>infinity</code>, which generates the Infinity object, with <code>inf</code> as an alias for the complete word. In general, words like these are not case-sensitive, so <code>INF</code> would produce <code>infinity</code>, as would <code>InfINIty</code> or any other capitalization of the word. |
||
+ | |||
+ | You can add your own words to the Context via the <code>add()</code> method. Usually, there is no data, so you simply use <code>{}</code> as the definition. To make a string case sensitive, use <code>{caseSensitive =>1}</code> as the data. To make a word create infinity, use <code>{infinite => 1}</code>. To make one word mean the same as another, use <code>{alias => "<i>name</i>"}</code>. 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, <code>A</code>, <code>B</code>, <code>C</code>, and <code>D</code> are now valid answers for a student to type, but they could also be <code>a</code>, <code>b</code>, <code>c</code>, or <code>d</code>. <code>True</code> and <code>False</code> (with any capitalization) are now available as well, along with <code>T</code> as an alternative to <code>True</code> and <code>F</code> as an alternative for <code>False</code>. The word <code>WeBWorK</code> can be entered, but only with this capitalization (as it should be). Finally, <code>unbounded</code> will produce the positive Infinity object (so <code>-unbounded</code> would be the negative Infinity object). |
||
+ | |||
+ | A string can be removed via the <code>remove()</code> 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 <code>set()</code> 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 <code>get</code> 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 <code>[[Common MathObject Methods#getFlag|getFlag()]]</code> 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 <code>tolerance</code>. This will be taken from <code>$correct</code> if it was set there, otherwise from the current answer checker's flags as passed to <code>$mo->cmp()</code>, and then from the Context for <code>$mo</code>. If the flag is not set in any of those, the value <code>.001</code> 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 <code>Complex</code> Context has <code>Re()</code> and <code>Im()</code> functions, as well as <code>arg()</code>, <code>mod()</code>, and <code>conj()</code>, and the <code>Vector</code> Context has <code>norm()</code> and <code>unit()</code>. These are not available in the <code>Numeric</code> 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 <code>sin()</code> in her answer or that would defeat the purpose. To remove a function, use the <code>disable()</code> method. For instance, |
||
+ | |||
+ | Context()->functions->disable("sin"); |
||
+ | |||
+ | makes the <code>sin()</code> 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 <code>remove()</code> 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 <code>enable</code>, 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#Disabling Functions|Answer Checkers and the Context]]. Some of the common ones are <code>Trig</code> to disable all trigonometric functions, <code>Numeric</code> to disable things like <code>ln()</code> and <code>sqrt()</code>, <code>Complex</code> to disable the complex functions, and <code>All</code> 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 <code>sqrt()</code> function, you may want to disable the exponentiation operators so that students can't use <code>a^(1/2)</code> or <code>a**.5</code> to produce square roots. Similarly, if you disable <code>abs()</code>, you would want to remove the absolute value vertical bars so that students can't use <code>|a|</code>. See [[Answer Checkers and the Context]] for details. |
||
+ | |||
+ | It is possible to add new functions in several ways. The <code>[http://webwork.maa.org/pod/pg/macros/parserFunction.html pg/macros/parserFunctions.pl]</code> 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 [[Modifying Contexts (advanced)#Adding_New_Functions| 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 <code>functions</code> object has the following: |
||
+ | |||
+ | {| class="wikitable" |
||
+ | ! Method !! Description |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->functions->disable("name")</code> |
||
+ | | style="padding:5px" | Marks the named function(s) or category of functions so that they can't be used in student answers. |
||
+ | |||
+ | |- style="vertical-align:top" |
||
+ | | style="padding:5px; white-space:nowrap" | <code>Context()->functions->enable("name")</code> |
||
+ | | style="padding:5px" | 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 <code>+</code> and <code>-</code> for addition and subtraction, <code>*</code> and <code>/</code> for multiplication and division, <code>^</code> or <code>**</code> for exponentiation, <code>!</code> for factorial, and <code>,</code> for forming lists. Some contexts include <code>U</code> for taking unions, <code>.</code> for taking dot products, and <code>><</code> 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 <code>undefine()</code> 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 <code>remove()</code> 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 <code>redefine</code>, 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 <code>sin(2x)</code> to be entered as <code>sin 2x</code>). 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)); |
||
− | * <code>Numeric</code> (variable x, no complex numbers, points, etc.) -- this is the default context |
||
+ | into the body of a PG problem. |
||
− | * <code>Complex</code> (variable z, i = \sqrt{-1}, no points, etc.) |
||
− | * <code>Point</code> (another name for Vector context, but angle brackets are not allowed) |
||
− | * <code>Vector</code> (variables x, y, z, angle brackets form vectors, i, j, k are unit coordinate vectors, etc.) |
||
− | * <code>Vector2D</code> (same as Vector, but with i and j in 2D) |
||
− | * <code>Matrix</code> (square brackets form matrices: [[0,1],[1,0]] ) |
||
− | * <code>Interval</code> (subsets of the real line: parens and brackets form intervals, finite subsets, (5,6] ,(-infinity,6), {5,6} etc. ) |
||
− | or one of the additional [[Specialized contexts]] that are available and defined in macros. In order to use one of these additional contexts, one needs to include the macro file for the context one wishes to use, with the syntax: |
||
+ | The [https://github.com/openwebwork/pg/tree/macros pg/macros/] directory contains a number of predefined contexts that limit the operations that can be performed in a student answer. For example, the <code>contextLimitedNumeric.pl</code> 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. |
||
− | loadMacros("contextLimitedPolynomials.pl"); |
||
+ | === Reduction Rules === |
||
− | The specific context in this case allows one to specify that only "simplified" polynomials in the form <math> a_n x^n + ... + a_0 </math> will be allowed as answers. Inspecting the file "contextLimitedPolynomials.pl" is one way to learn how to modify a context to meet your |
||
+ | When random numbers are used as coefficients in Formulas, it is possible to get situations like <code>1 x^2 + -3 x + 0</code>, and it looks bad to have the coefficient of 1, the <code>+ -</code>, and the <code>+ 0</code> as part of the formula. For that reason, the Formula class has a <code>reduce()</code> method that will normalize the Formula to remove such issues. The reduction rules are controlled through the Context's <code>reduction</code> object. This lists the various reduction rules and determines which ones are active. You can turn these on and off using the <code>set()</code> method, as in |
||
− | requirements. |
||
− | ===Common Context methods=== |
||
+ | Context()->reductions->set("(-x)-y" => 0); |
||
− | ====Producing "TeX" strings==== |
||
− | This is the most common use of a Context method. |
||
− | Suppose that you have a formula such as |
||
− | $f = Formula("sin(3x^2)/cos(x)"); |
||
+ | to turn off the reduction that converts <code>(-x)-y</code> to <code>-(x+y)</code>. As usual, you can set the values for multiple rules in one <code>set()</code> call. |
||
− | Normally "$f" emits the string <code>"sin(3x^2)/cos(x)"</code> |
||
+ | Because the <code>set()</code> call is rather verbose, there are shorthands for turning reduction rules on and off via the <code>reduce()</code> and <code>noreduce()</code> methods. For example, |
||
− | which defines the function. However if you set |
||
− | the current context to "texStrings": <code>Context()->texStrings;</code> |
||
− | then "$f" produces the TeX string used to typeset the function: |
||
− | <code>"\frac{ \sin(3x^2) }{\cos(x) } "</code> |
||
− | In practice this means that virtually every |
||
+ | Context()->noreduce("(-x)-y"); |
||
− | occurence of a BEGIN_TEXT/END_TEXT block should be written as |
||
− | Context()->texStrings; |
||
+ | is equivalent to the <code>set()</code> above. Again, you can supply multiple rules at once: |
||
− | BEGIN_TEXT |
||
− | Differentiate the function \( $f\) ... |
||
− | ...... |
||
− | END_TEXT |
||
− | Context()->normalStrings; |
||
− | This means that the "$f" is replaced by TeX code placed between the "LaTeX math" symbols \( and \) |
||
+ | Context()->noreduce("(-x)-y","(-x)+y"); |
||
− | and is then transformed into a typeset representation of the formula "$f". |
||
− | It is a good idea to return to "normalStrings" outside BEGIN_TEXT |
||
+ | 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. |
||
− | blocks since the normal string representation |
||
− | of $f may be required inside perl macros in the rest of the problem. |
||
− | ====Context changes for Formulas==== |
||
+ | $f->reduce("(-x)-y" => 0, "(-x)+y" => 0); |
||
− | ;Adding variables |
||
+ | which turns off the reduction rules only for this one reduction. |
||
− | : In the Numeric Context, the default variable is <code>x</code>. Use the <code>variables->add(y=>'Real')</code> method to specify additional legitimate variables and their type in the current context. <br/>For example: |
||
− | : <code>Context()->variables->add(y=>'Real'); </code> |
||
− | : <code>Context()->variables->add(y=>'Real',z=>'Real'); </code> |
||
− | : <code>Context()->variables->add(z=>'Complex'); </code> |
||
− | |||
− | ;Setting variables |
||
− | : To set the variable(s) in the current Context (replacing the default variable(s)), use the <code>variables->are()</code> method, as shown in the following: |
||
− | : <code>Context()->variables->are(y=>'Real'); </code> |
||
− | : <code>Context()->variables->are(y=>'Real',z=>'Real'); </code> |
||
− | : <code>Context()->variables->are(z=>'Complex'); </code> |
||
− | |||
− | ;Setting variable limits |
||
− | : The limits for the variables in the Context determine the values that may be used to determine the correctness of a Formula. If a Formula is not well-defined on the default range [-2,2], it may be useful to set a different range for the variable(s) in the Context: |
||
− | : If the only variable in the Context is <code>x</code>, |
||
− | :: <code>Context()->variables->set(x=>{limits=>[-1,1]});</code> |
||
− | : We can similarly set limits for several variables at once, if they have been defined in the Context: |
||
− | :: <code>Context()->variables->set(x=>{limits=>[-1,1]},y=>{limits=>[-1,1]});</code> |
||
− | ====Context changes for Strings==== |
||
+ | 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, |
||
− | ;Adding Strings to the Context |
||
− | : By default there are a limited number of Strings that are predefined in the Context. In the Numeric Context (the default) these include <code>inf</code>, <code>infinity</code>, and <code>DNE</code>. Because they exist in the Context, students can enter these as answers without generating error messages (though, of course, the answer may be incorrect). To add other Strings to the Context, we use the <code>strings->add()</code> method: |
||
− | * To add the string "Continuous" to the Context, |
||
− | Context()->strings->add(Continuous=>{}); |
||
− | * To add the strings "True" and "T" to the Context, making "T" be an alias for "True" (so that a student could enter either "True" or "T" as an answer and have either marked correct), |
||
− | Context()->strings->add(True=>{},T=>{alias=>'True'}); |
||
− | * And to add "True", "False" and aliases for both, |
||
− | Context()->strings->add(True=>{},False=>{},T=>{alias=>'True'},F=>{alias=>'False'}); |
||
− | ;Changing case-sensitivity of Strings |
||
− | :By default, WeBWorK regards String objects as being case-insensitive. Thus if the correct answer to a problem is the String object "True", a student could respond "True" or "true" and have either answer marked correct. To change this behavior, we set the <code>caseSensitive</code> flag '''while adding the variable to the Context''': |
||
− | Context()->strings->add(True=>{caseSensitive=>1}); |
||
− | ====Other useful Context changes==== |
||
+ | $a = 2; $b = 3; |
||
− | ;Preserving constant values in Formulas |
||
+ | $f = Formula("$a * $b * x + 4 * $a"); |
||
− | : This technique helps to make the correct answer shown to students educationally useful. |
||
+ | $g = Formula("abs(-$b)"); |
||
− | : The answer <code>0.5</code> might be correct but it is not as enlightening as <code>sin(pi/6)</code>. |
||
− | By default WeBWorK will reduce constant expressions that are ''substituted'' into Formulas. This is not always what we want to do. It's a somewhat subtle point, which may be easiest to consider in the context of an example. |
||
+ | would produce the formula <code>6 * x + 8</code> rather than <code> 2 * 2 * x + 4 * 2</code> for <code>$f</code>, and <code>3</code> rather than <code>abs(-3)</code> for <code>$g</code>. |
||
− | * Consider the WeBWorK code |
||
+ | These operations are controlled by two Context flags (not reduction rules, since they apply to all parsing, not just <code>reduce()</code> calls). These are the <code>reduceConstants</code> and <code>reduceConstantFunctions</code> flags. You can unset these to prevent those reductions from occurring automatically, as in |
||
− | $f = Formula("sin(x)"); |
||
− | $f0 = $f->eval(x=>pi/6); |
||
− | displayed as: (0.5) |
||
− | By evaulating the sine function at the the Real value <code>pi/6</code> we obtain a real number. |
||
− | <code>$f0</code> is accordingly a Real value (0.5) which will be displayed as a decimal. |
||
− | We can make WeBWorK display the unreduced expression <code>sin(pi/6)</code> by setting the |
||
+ | Context()->flags->set( |
||
− | <code>reduceConstants</code> flag in the Context and |
||
+ | reduceConstants => 0, |
||
− | '''substituting''' a Formula for <code>x</code> instead of '''evaluating''' the expression: |
||
+ | reduceConstantFunctions => 0, |
||
+ | ); |
||
+ | $f = Formula("(1+sqrt(5))/2)"); |
||
− | Context()->flags->set(reduceConstants=>0); |
||
+ | in which case <code>$f</code> would remain <code>(1 + sqrt(5))/2</code> rather than the usual <code>1.61803</code>. |
||
− | $f = Formula("sin(x)") |
||
− | $f0 = $f->substitute(x=>Formula("pi/6")); |
||
− | displayed as: sin(pi/6) |
||
− | Here the Formula for <code>pi/6</code> is substituted into the function <code>$f</code>. <code>$f0</code> is a new Formula which when displayed appears as <code>sin(pi/6)</code> not in the reduced form (0.5). |
||
+ | The names of all the reduction rules in the Context can be displayed by placing |
||
− | =====Summary: evaluate vs. substitute===== |
||
+ | TEXT(join(',',Context()->reductions->names)); |
||
− | *The result of <code>$formula->evaluate(45)</code> has the type obtained by evaluating the formula at 45. It is no longer |
||
+ | into the body of a PG problem. There is also an annotated available on the [[Reduction rules for MathObject Formulas]] page. |
||
− | a formula, but has the type of the range of the original formula. |
||
− | *The result of <code>$formula->substitute(45)</code> is still a formula, with a string representation in which each <code>x</code> has been replaced by 45. |
||
− | + | == See Also == |
|
− | [[ContextFlags]] |
||
− | [[ModifyingContexts(Advanced) ]] |
||
+ | * [[Common Contexts]] |
||
+ | * [[Context flags]] |
||
+ | * [[Specialized contexts]] |
||
+ | * [[Modifying contexts (advanced)]] |
||
[[Category:Contexts]] |
[[Category:Contexts]] |
Latest revision as of 13:44, 22 February 2023
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.