WeBWorK Problems

Handing Context in a subroutine

Handing Context in a subroutine

by Michael Schroeder -
Number of replies: 18
I am wanting to write a question like

Find the equation of the line parallel to $x+y=4$ and going through (2,4).

I want the solution to be inputted in a way so that one side of th equation would be zero, so for this problem's solution (x+y=6), I would like to accept

x+y-6, 2x+2y-12, -x-y+6, etc.

So I would like to use an adapted parameter, and I've implemented this.
However, if a student simply puts in 0, then their answer is also scored as correct.
So I tried tinkering with the routine to make 0 not be a correct answer.

I would like to make this routine available for a variety of problems, and I don't want to cut and paste the entire subroutine in each .pg file, so I want to write a macro for it. In order to use this adaptive parameter, I need access to the context of the problem, so I came up with the following code for my macro:

sub scalarEquation_cmp {
my ($correct, $student, $ah ) = @_;
return 0 if $student == Formula(0);
my $context = $ah->{correct_value}->{context}->copy;
$context->flags->set(no_parameters=>0);
$context->variables->add('C0'=>'Parameter');
$student = Formula($context,$student);
$correct = Formula($context,"C0*(".$ah->{correct_value}.")");
return $correct == $student;
}

So my question is: is this the best way to code this? Mind you, I'm trying to maintain another's code, and so my follow-up question is: is there a better way to format the question to check the answer? I should say we are emphasizing the Ax+By+C=0 format for the equation of a line, so I would like to guarantee the student's input is of that form.

Thanks,

Mike Schroeder
In reply to Michael Schroeder

Re: Handing Context in a subroutine

by Davide Cervone -
What you have done is fine, but I would recommend some slight changes:
    sub scalarEquation_cmp {
      my ($correct, $student, $ah ) = @_;
      if ($student == Formula(0)) {
        return 0 if $ah->{isPreview};
        Value->Error("Your answer should be a formula that is not always 0");
      }
      my $context = $correct->context->copy;
      $context->flags->set(no_parameters=>0);
      $context->variables->add(C0=>'Parameter');
      $student = Formula($context,$student);
      $correct = Formula($context,"C0*$correct");
      return $correct == $student;
    }
Here we produce a hint if the student enters a formula equivalent to 0 rather than just marking it incorrect. You can also use $correct for the correct answer rather than having to go to $ah->{correct_value}. I would have recommended using
    $student = $student->inContext($context);
    $correct = ($correct->inContext($context))*"C0";
as being more efficient internally, except that inContext() was broken for Formula obejcts (ouch). I have just corrected that in the HEAD version of the CVS archive, but your approach works fine. Note that I have substituted $correct into the string rather than using the dot notation; Formulas automatically insert parentheses for themselves in such circumstances so you can do exactly this.

Hope that helps.

Davide

PS, if you do

    $scalarEquation_cmp = sub scalarEquation_cmp {
       ...
    };
then you can use
    ANS($f->cmp(checker=>$scalarEquation_cmp));
without the nasty ~~& notation.

PPS, have you considered using the parserImplicitPlane.pl macros instead, which also implement implicit lines (or hyperplanes, or linear objects in any dimension)? See the documentation for more details.

In reply to Davide Cervone

Re: Handing Context in a subroutine

by Michael Schroeder -
With either my original code or the snippet you sent, I get the following message when I implement it:

Please inform your instructor that an error occurred while checking your answer at [PG]/lib/Value/AnswerChecker.pm line 247

I've tried to run this down, but to no avail. Any ideas?

Mike
In reply to Michael Schroeder

Re: Handing Context in a subroutine

by Davide Cervone -
Both your original and my replacement work for me. What version of WeBWorK are you using? Can you send the complete problem you are working on? It may help to see exactly how you are calling things.

Davide
In reply to Davide Cervone

Re: Handing Context in a subroutine

by Michael Schroeder -
Here is the code for one of the questions for which I would like to use this routine. With the current commenting, the problem does not use the routin

-Mike

## DESCRIPTION

## [N,S] Find equation of the line in form Ax + By + C = 0
## given a point and "parallel to..."
## ENDDESCRIPTION

## KEYWORDS('line')

## DBsubject('Algebra')
## DBchapter('Equations')
## DBsection('Linear')
## Date('Monday, 4 June 2007')
## Author('Rob Owen')
## Institution('UW-Madison')
## TitleText1('College Algebra')
## EditionText1('Fifth Edition')
## AuthorText1('David Cohen')
## Section1('1.6')
## Problem1('1.6.28')

DOCUMENT();

loadMacros(
"PGbasicmacros.pl",
"MathObjects.pl",
"UWMadisonMacros.pl");

$showPartialCorrectAnswers = 1;

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

$absx = 3;
$y = 4;

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

TEXT(beginproblem());

BEGIN_TEXT

$PAR

Find the equation of the line that passes through the point
\( (-$absx, $y) \) and is parallel to the \( y \)-axis.
Write your answer in the form \( A x + B y + C = 0 \).

$PAR

\( \hspace{.25in} \mbox{Line is:} \) \{ ans_rule(20) \} \( = 0 \)

END_TEXT

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

$f = Compute("x + $absx");
ANS($f->cmp(checker=>~~&scalarMultipleEquation_cmp));

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

ENDDOCUMENT();


In reply to Davide Cervone

Re: Handing Context in a subroutine

by Michael Schroeder -
Also, I believe we are using 2.4.

Mike
In reply to Michael Schroeder

Re: Handing Context in a subroutine

by Davide Cervone -
Sorry for the huge delay in getting back to you on this. If you are still interested in fixing the problem, I have figures out what it is.

WeBWorK attempts to inform a student when they enter an equivalent (not not identical) expression (to help them learn what differences are actually important in an expression), and to do the test, it does a comparison of the old student answer with the new one. It does this using the answer checker so that "equivalent" is interpreted as it is in the actual problem. Unfortunately, the first time through, there IS no old student answer, but the custom checker is called anyway, and the error message you are getting is due to $correct not being defined in that case (it is supposed to be the old answer).

The fix is simple enough; just at one line at the top of the checker:

    sub scalarMultipleEquation_cmp {
      my ($correct, $student, $ah ) = @_;
      return 0 unless defined $correct;
      if ($student == Formula(0)) {
        return 0 if $ah->{isPreview};
        Value->Error("Your answer should be a formula that is not always 0");
      }
      my $context = $correct->context->copy;
      $context->flags->set(no_parameters=>0);
      $context->variables->add(C0=>'Parameter');
      $student = Formula($context,$student);
      $correct = Formula($context,"C0*$correct");
      return $correct == $student;
    }
I didn't catch it when I was testing the routine because by the time I had the correct routine, there was already a previous answer. :-)

Davide

In reply to Davide Cervone

Re: Handing Context in a subroutine

by Patti Lamm -
This removes the warning unless the student initially enters the answer 0 (i.e., their equation is 0=0). If they then follow up with the correct answer, the same warning (about line 247 in the AnswerChecker.pm) appears.

Is there a fix for this? Thanks.
In reply to Patti Lamm

Re: Handing Context in a subroutine

by Davide Cervone -
When the student has entered "0" as the previous answer, the equivalent-answer check uses "0" as the correct answer and the student's new answer as the student answer. This means that the "correct" answer is C0*0 and solving for the C0 turns out to cause an error internally. I've have to track that down at some point (some linear equation can't be solved, no doubt, but it should give a message rather than fail outright).

A solution is to add a check for whether the CORRECT answer is zero as well:

    sub scalarMultipleEquation_cmp {
      my ($correct, $student, $ah ) = @_;
      return 0 unless defined $correct;
      if ($student == Formula(0)) {
        return 0 if $ah->{isPreview};
        Value->Error("Your answer should be a formula that is not always 0");
      }
      return 0 if $correct == Formula(0);
      my $context = $correct->context->copy;
      $context->flags->set(no_parameters=>0);
      $context->variables->add(C0=>'Parameter');
      $student = Formula($context,$student);
      $correct = Formula($context,"C0*$correct");
      return $correct == $student;
    }

I think that should take care of it for you.

Davide

In reply to Davide Cervone

Re: Handing Context in a subroutine

by Patti Lamm -
Thanks for your reply, Davide. Actually, I still get the error when submitting any answer following the answer of 0=0. I also get the error if I submit

x*right_answer

first, then follow with

scalar*right_answer

(where right_answer is, obviously, the formula for the correct answer, expressed in terms of the variables x and y). Am I just unlucky?
In reply to Patti Lamm

Re: Handing Context in a subroutine

by Davide Cervone -
I am not able to reproduce either error that you are seeing.

Can you send the current version of you problem file and the answer checker, so I'm sure we are working with the same thing? Also, the problem seed that you are using, in case that makes a difference? Finally, what message is in the results table at the top of the page when you get the error about reporting the problem to the instructor? (It should include the actual perl error message there.)

Thanks.

Davide
In reply to Davide Cervone

Re: Handing Context in a subroutine

by Patti Lamm -
Thanks for being so attentive to this, Davide. Here is my code:


DOCUMENT();

loadMacros(
"Parser.pl",
"PG.pl",
"MathObjects.pl",
"PGstandard.pl",
"PGchoicemacros.pl",
"PGbasicmacros.pl"
);

sub scalarMultipleEquation_cmp {
my ($correct, $student, $ah ) = @_;
return 0 unless defined $correct;
if ($student == Formula(0)) {
return 0 if $ah->{isPreview};
Value->Error("Your answer should be a formula that is not always 0");
}
return 0 if $correct == Formula(0);
my $context = $correct->context->copy;
$context->flags->set(no_parameters=>0);
$context->variables->add(C0=>'Parameter');
$student = Formula($context,$student);
$correct = Formula($context,"C0*$correct");
return $correct == $student;
}



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

Context("Numeric");
Context()->variables->are(x=>'Real',y=>'Real');
Context()->flags->set(reduceConstants => 0);
Context()->flags->set(reduceConstantFunctions => 0);

$a = random(2,5,1);
$b = random(2,6,1);

$x_disp = "$b \cos $a t";
$y_disp = "$b \sin $a t";
$end_t = Formula("pi/$a");
$end_t_disp = "\frac{\pi}{$a}";

$left_hand_side = Formula("x^2 + y^2 - $b^2");
$x0=Formula("-$b");
$x1=Formula("$b");
$y0=Formula("0");
$y1=Formula("$b");

##########################################
# Multiple choice:

$correct_ans = "the movement is counter-clockwise";
$question = "(1) As \(\;t\;\) increases on \(\left[0, $end_t_disp\right]\), in which direction is the point \( (x(t), y(t))\) moving?";

$mc = new_multiple_choice();
$mc->qa("$question","$correct_ans");
$mc->extra("the movement is counter-clockwise","the movement is clockwise");
$mc->makeLast("the movement is counter-clockwise","the movement is clockwise");

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

TEXT(beginproblem());
Context()->texStrings;

BEGIN_TEXT
$PAR

A particular curve is represented parametrically by
$PAR
\( x = $x_disp\), \( \;\;\; y=$y_disp, \) \(\;\;\; t\in \left[0, $end_t_disp\right].\)
$PAR
----------------------------------------------------------------------------------
$PAR

\{ $mc->print_q() \}
\(\;\;\;\) \(\;\;\;\) \(\;\;\;\)\(\;\;\;\) \{ $mc->print_a() \}
$PAR
----------------------------------------------------------------------------------
$PAR

(2) What is the corresponding Cartesian equation for this curve (the equation in \(\;x\;\) and \(\;y\;\) only)?

$PAR
\(\;\;\;\) \(\;\;\;\) \(\;\;\;\) Cartesian equation: \{ ans_rule(30) \} \(\;=0\).
$PAR

$BITALIC Write your answer as an expression in \(\; x \;\) and \(\; y \;\) which is the left-hand side of the equation when the right-hand side is set equal to zero. For example, the equation of the parabola \(y = x^2+1\) can be rewritten as \(y-x^2-1 =0 \), so your answer would be \(y-x^2-1\). Write the equation of the full curve, even if only part of the curve is given by the parametrization. $EITALIC
$PAR
----------------------------------------------------------------------------------
$PAR

(3) Give the smallest and largest values of \(y\) taken by this curve (both $BITALIC inf $EITALIC and $BITALIC -inf $EITALIC are possible answers.)

$PAR
\(\;\;\;\) \(\;\;\;\) \(\;\;\;\)
\{ans_rule(5)\} \(\le y \le \) \{ans_rule(5)\}.

$BR ---------------------------------------------------------------------------------- $BR

END_TEXT

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

Context()->normalStrings;
Parser::Number::NoDecimals();
Context()->flags->set(formatStudentAnswer=>'parsed');
Context()->flags->set(
tolerance => .000001,
tolType => 'relative',
limits => [-4,4],
num_points => 5,
);

ANS(radio_cmp( $mc->correct_ans() ) );
ANS($left_hand_side->cmp(checker=>~~&scalarMultipleEquation_cmp));
ANS($y0->cmp);
ANS($y1->cmp);

############################################
Context("Numeric");
Context()->texStrings;

#############################################
#### the written explanation + answer is given below
#### -- it's available for TAs and professor at any time (complete answer)
#### -- it's available for students when the date for solutions has passed
#### (only a brief guide to the answer is given for the students; they
#### can always see the final numerical answer in the answer box).

Context("Numeric");
Context()->texStrings;

if ($permissionLevel >= 5) { #solution for TAs and professor

SOLUTION(EV3(<<'END_SOLUTION'));
$PAR SOLUTION for PROFESSORS and TAs $PAR
The curve is an upper-semi-circle of radius $b$, with formula \(x^2 + y^2 - $b^2 = 0\).

END_SOLUTION

}

install_problem_grader(sub {
my ($result,$state) = std_problem_grader(@_);
my $time = time();
my $open = $time >= $openDate && $time <= $dueDate;
my $submit = $inputs_ref->{submitAnswers};
my $attempts = $state->{num_of_correct_ans} + $state->{num_of_incorrect_ans};
$attempts-- if $attempts && !$submit;

my @msg = ();
push(@msg,"Your score was ".($open ? "" : "not "). "recorded.") if $submit;
push(@msg,"You have attempted this problem $attempts time".($attempts == 1 ? "." : "s."));
if ($submit) {
if ($result->{score} == 1) {
push(@msg,"You received a score of 100% for this attempt.");
push(@msg,"Your overall recorded score is 100%.");
} else {
push(@msg,"Your answers are not yet fully correct.");
}
}
unless ($open) {
push(@msg,"The homework set is not yet open.") if $time < $openDate;
push(@msg,"The homework set is closed.") if $time > $dueDate;
}
$result->{summary} = '<DIV CLASS="ResultsWithError">Not all the answers above are correct.</DIV>' unless $result->{score} == 1;
$state->{state_summary_msg} = join('<br>',@msg);
return ($result,$state);
});

ENDDOCUMENT();
In reply to Patti Lamm

Re: Handing Context in a subroutine

by Patti Lamm -
The seed is 2532, but the warning error doesn't seem to be seed-dependent.

If the answer 0 (i.e., for 0=0) is given and checked, followed by an answer which is twice the correct answer (or even equals the correct answer), a pink screen appears and the following warning is given:

Warning messages

  • Please inform your instructor that an error occurred while checking your answer at [PG]/lib/Value/AnswerChecker.pm line 247

(In my testing, the other 3 answers for the problem are left blank.)


Here's the result table at the top of the warning page -- I'm not sure what you mean about a perl error appearing here:


Entered Answer Preview Correct Result
A incorrect
2*[(x^2)+(y^2)-(6^2)] 2\!\left(x^{2}+y^{2}-6^{2}\right) x^2+y^2-6^2 correct
0 incorrect


6 incorrect

In reply to Patti Lamm

Re: Handing Context in a subroutine

by Davide Cervone -
OK, the code helped, because it seems to be an issue of the answer checker interacting with the rest of the problem.

The problem seems to be the extraneous

    Context("Numeric");
    Context()->texStrings;
near the bottom of the problem (there are two copies). These are not needed since you don't make any new MathObjects after that, but the fact that the context has changed is, I think, causing the Formula comparisons to fail in the checker, since formulas can only be compared to ones in the same context, and the Formula(0) calls will create ones in the current context, not the context of the correct answer, when the checker is called to test the past answer against the current answer. (The current context is set to that of the correct answer during the check of the student's answer to the professor's answer, but not when the previous student answer is checked against the current one.)

The solution is to force the Formula(0) into the correct context. Here is some more code to try:

    sub scalarMultipleEquation_cmp {
      my ($correct, $student, $ah ) = @_;
      return 0 unless defined $correct;
      my $context = $correct->context;
      if ($student == Formula($context,"0")) {
        return 0 if $ah->{isPreview};
        Value->Error("Your answer should be a formula that is not always 0");
      }
      return 0 if $correct == Formula($context,0);
      $context = $context->copy;
      $context->flags->set(no_parameters=>0);
      $context->variables->add(C0=>'Parameter');
      $student = Formula($context,$student);
      $correct = Formula($context,"C0*$correct");
      return $correct == $student;
    }
At least this seems to work for me. Hope it does for you, too.

Davide

PS, the reason that you didn't see an error message in the results table is that the failure wasn't during the main check (student vs. professor) but in the check for equivalent answers (student vs past student), which doesn't add anything to the results table. Sorry for the confusion on that.

In reply to Davide Cervone

Re: Handing Context in a subroutine

by Patti Lamm -

The problem seems to be the extraneous

 Context("Numeric");
Context()->texStrings; A-ha, yes. It's working now -- many thanks! -Patti
In reply to Patti Lamm

Re: Handing Context in a subroutine

by Davide Cervone -
A couple of notes on the code itself:

  • $end_t_disp could be obtained from $end_t->TeX to be sure that the two formulas always correspond correctly. Alternatively, if you set Context()->texStrings before defining $question, you could just use the value of $end_t directly, as it would turn itself into a TeX string when inserted into the question.

I don't think you want to use Formula's for $x0, $x1, $y0, and $y1, as these will produce the wrong type of error messages when students type the wrong things. You should use Real() or Compute() to get these values, as in
    $x0 = Real(-$b);
    $x1 = Real($b);
    $y0 = Real(0);
    $y1 = Real($b);

Davide

In reply to Davide Cervone

Re: Handing Context in a subroutine

by Patti Lamm -
Thanks for the TeX pointers -- I didn't realize that I didn't need to work so hard at this.

As for the Formula with $x0, etc -- point taken. I should mention that this code is a template for several different problems where the $x0 variable is actually a formula in some cases; so I kept the "Formula" in place even when constants were used. Can you give me an example of the wrong type of error message that might be produced if a student gives an incorrect answer? So far I haven't run into this.

Many thanks for the help.
-Patti
In reply to Patti Lamm

Re: Handing Context in a subroutine

by Davide Cervone -
If the student types "1,2" as an answer, she would get a message indicating that "your answer isn't a formula returning a number (it looks like a list of numbers)", whereas the better message would be that "your answer isn't a number (it looks like a list of numbers)". She should not get a message suggesting a formula returning a number is the appropriate response. Subtle, but important, I think.

Davide