WeBWorK Problems

Undefined subroutine error when adding function to context

Undefined subroutine error when adding function to context

by Joel Therrien -
Number of replies: 3
trying to take a step back from my more ambitious attempt to make a specialized context, I decided to see how to just add a formula to an existing context worked.

I used the code shown in "Adding new functions" in the webwork wiki. But that fails in a similar manner to what I was experiencing in the other problem I posted yesterday.

The error I get is this:

If I try to use log2() in the problem.

Any idea why this is happening?

The pg code is below:

#############################################
#  Initialization

DOCUMENT();

loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"AnswerFormatHelp.pl",
"PGgraphmacros.pl",
"unionTables.pl",
"parserFunction.pl"
);

TEXT(beginproblem());

package my::Function::numeric;
   our @ISA = ('Parser::Function::numeric');     # subclass of Parser::Function::numeric
 
   my $log2 = CORE::log(2);                      # cached value of log(2)
 
   sub log2 {                                    # the routine for computing log base 2
     shift; my $x = shift;
     return CORE::log($x)/$log2;
   }
 
   package main; 

Context("Numeric");

 Context()->functions->add(
     log2 => {class => 'my::Function::numeric', TeX => '\log_2', nocomplex => 1},
   );

$refreshCachedImages = 1;

##############################################
#  Setup

$tst = log2(2); # 

#############################################
#  Main text

Context()->texStrings;
BEGIN_TEXT

test

Test 1 = $tst

END_TEXT
Context()->normalStrings;

###############################################
#  Answer evaluation

$showPartialCorrectAnswers = 1;

############################
#  Solution

ENDDOCUMENT();

In reply to Joel Therrien

Re: Undefined subroutine error when adding function to context

by Paul Pearson -
Hi,

The example here may be similar to what you're looking for:

http://webwork.maa.org/wiki/TrigFunctionsInDegrees


##################################

Originallly, I misread your question (it's late right now) and gave a different answer:

You are making this more complicated than it needs to be.  Using MathObjects, the easiest way to define a new named function that is added to the context is given by the following example.  (I produced this example by modifying http://webwork.maa.org/wiki/AddingFunctions .)

Best regards,

Paul Pearson

###############
DOCUMENT();

loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"parserFunction.pl",
);

TEXT(beginproblem());

Context("Numeric")->variables->add(y=>"Real");
parserFunction("log2(x)" => "log(x)/log(2)");

$answer = Compute("log2(2)");

Context()->texStrings;
BEGIN_TEXT

Test 1 = $answer
\{ ans_rule() \}
Note: You can enter \( \log_2(a) \) as ${BTT}log2(a)${ETT}.
END_TEXT
Context()->normalStrings;

ANS($answer->cmp);

ENDDOCUMENT();
In reply to Joel Therrien

Re: Undefined subroutine error when adding function to context

by Davide Cervone -
Paul's is a good solution when the function's can be written as a formula like what a student would enter in an answer blank, as it is in this case. But in the event that you need to do something algorithmic or that can't be written as a simple formula, your original approach is the one to take.

Note that math expressions in a PG problem can be computed in two very different ways: using native Perl code, or using MathObjects. The MathObject library tries to make these two as similar as it can, but you do need to be aware of the differences. I'm going to write a longish explanation below, and you will already know a lot of it, but there are some points that are important to my answer at the end.

When you write

    $y = log($x)/log(2);
for example, you are using Perl mathematical computation. (Perl variables start with a dollar sign, you have not used Compute() or Formula(), or quotation marks, etc.). Note that in Perl expressions, exponentiation is done via ** not ^, and multiplication is explicit, so you must write
    $y = 3*$x**2;
not y = 3x^2 when you are doing perl mathematics.

MathObjects math is generally done through Compute() or Formula() or one of the other constructor functions (Real(), Complex(), Interval(), etc.). These take a string representation of the expression and convert it into an object that represents the result. So you would write

    $f = Compute("log(x)/log(2)");
to get a Formula object representing the given expression, and you can do
    $y = Compute("3x^2");
to get a formula for this quadratic.

The connection between the Perl and MathObject formulas, and a source of part of the confusion, is that once you have a MathObject, if you use it in a Perl formula, the result will be a MathObject for the Perl formula. For example

    $x = Compute("x");
    $f = log($x)/log(2);
is effectively the same as
    $f = Compute("log(x)/log(2)");
(The first line creates a Formula() for the identity function and calls that $x in Perl, and when that is used in log($x) the result is a Formula for the log of x. When that is divided by the log of 2, the result is a Formula comparable to the one given above). This is one of the powerful features of MathObjects, but it is also one of the places where confusion can occur.

The meaning of expressions computed by MathObjects are controlled by the MathObject Context(). This defines what operators, functions, variables, and so on, are allowed in the formulas that are produced by Compute() and the various constructor functions.

When you do

    Context()->functions->add(
      log2 => {class => 'my::Function::numeric', TeX => '\log_2', nocomplex => 1},
    );
you are adding a function named log2 to the context, so that it can be used in formulas produced by Compute() (and since student answers are processed by Compute(), you are adding log2 to the things they can type in their answers).

Note, however, that you are not modifying perl expressions by doing this, only MathObjects. Native perl code does't know about MathObjects contexts, and so you have not added log2() to the things you can type in a Perl expression. If you do

   $y = log2($x);
at this point, you will get the message about main::log2 not being defined that you list above. That is because log2 is only defined within the MathObject context, not in native Perl expressions, and the expression above is a native Perl one, not a MathObject one.

To make log2() available in native Perl, you must also define a native Perl function. Note that near the bottom of the section on adding functions that you link to in your message, it says that you need to use

    sub log2 {Parser::Function->call("log2",@_)}
in order to be able to use log2()
In reply to Davide Cervone

Re: Undefined subroutine error when adding function to context

by Joel Therrien -
Oh ho! Thanks Davide,

I think that may well be the issue right there. I'll try that and see where it gets me.

And yes, your initial assessment is right: I am looking to have the possibility of having a more formulaic approach for some functions (for example tunneling with energies above or below a barrier use different formulae). That and not having to repeat each equation for every problem is appealing, thus the initial try at rolling my own context.

Joel