WeBWorK Problems

Checking submitted answers into a formula

Checking submitted answers into a formula

by John Travis -
Number of replies: 9
This may be a real easy problem to fix but I can't find a solution on the wiki.  I would like to take an answer entered as a point (x,y) and check to see if that answer is a solution to an equation of the form f(x,y) = constant.

For example, if I were trying to see if an entered answer falls on a circle with radius sqrt(r), my attempt has been
ANS($r->cmp( $f->eval( 
     x=>\{ ANS_NUM_TO_NAME(1) \},
     y=>\{ ANS_NUM_TO_NAME(2) \} ) )
);
which of course does not work since I'm asking here.  Of course I'd really like to also get rid of any direct reference to the entered answers.

Thanks,

JT
In reply to John Travis

Re: Checking submitted answers into a formula

by Davide Cervone -
Look at the parserSolutionFor.pl macro file. There is documentation on line for it. I think it will do what you want.

Otherwise, you would need to make a Point object with a custom checker that checks to see if the point satisfies your formula. That is also possible, but why bother when it is already done for you by parserSolutionFor.

Davide
In reply to Davide Cervone

Re: Checking submitted answers into a formula

by Robin Cruz -

I'm trying to do some similar to what Travis is doing, but the parserSolutionFor.pl won't work in my case.  Here's my problem:

--The first answer box, students enter a function in the form: P(t) = $a($b/$a)^(t/10)

--In the second answer box, they find the value of the function at a value: t = 20.

Depending on the values, it's possible due to rounding in the student's part to get the correct answer in the first box, but not the second.  I don't want to require the student enter an exact value in the first box, but I'd like to tell them they need more accuracy if their answer in the second box is going to be too far off.  I could give them an error message in the second box, but it would be better to have the error message where it "belongs".

I put an example of the code below.

Thanks -- rac

-----------------------------------------------------------

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

TEXT(beginproblem());

$a=random(10,40,5);
$b=random(50,80,5);  $a=21; $b=83;

Context()->texStrings;
BEGIN_TEXT
The population of a region is growing exponentially. There were $a
million people in 1980 (when \(t=0\)) and $b million people in 1990.
$PAR
(a) Find an exponential model, \(P(t)\), for the population (in millions of people)
at any time \(t\), in years after 1980.  NOTE: Keep at least 4 decimal places in your answer.
$BR
Answer: \{ans_rule(35)\}
$PAR
(b) What population do you predict for the year 2000? NOTE: Round to one decimal place.
$BR
Predicted population in the year 2000 = \{ans_rule(25)\}
END_TEXT
Context()->normalStrings;

parser::Assignment->Allow;
Context()->variables->are(t=>'Real');
parser::Assignment->Function("P");

$ans_a = Compute("P(t)=$a*($b/$a)^(t/10)");
ANS($ans_a->cmp);  #PUT IN A CHECK FOR t=2 AND APPROPRIATE ERROR MSG


$ans_b = Compute("round(10*$a*($b/$a)**(2))/10");
ANS($ans_b->cmp);

ENDDOCUMENT();

In reply to Robin Cruz

Re: Checking submitted answers into a formula

by Robin Cruz -

Oops, I forgot I used a slightly modified version of "parserAssignment.pl" which I called "contextFunctionAssign.pl". It just has a more customized error message and otherwise is "parserAssignment.pl

--rac

In reply to Robin Cruz

Re: Checking submitted answers into a formula

by Davide Cervone -
Here is one approach:
    ANS($ans_a->cmp(checker => sub {
      my ($correct,$student) = @_;
      return 0 unless $correct == $student;
      my $Y = ($correct->eval(t=>20)->value)[1];
      my $y = ($student->eval(t=>20)->value)[1];
      Value->Error("Your answer is not precise enough.  Try using more digits.") unless $Y == $y;
      return 1;
    }));
This uses a custom answer checker that first checks if the student answer is correct as a function (but as you point out, it may be too forgiving to get the second part right).

If they are "equal", it gets the correct and student values at t = 20. The result of evaluating the Assignment object is actually still an assigment, so we take its value, which gives back the internal data; the formula's value is the second of these.

If the student answer doesn't match the correct one at t=20, then you give your error message (modify to suit). Finally, if we don't give the message, return that the answer is correct.

An alternative would be to force t=20 to be one of the test points, so that you are sure that the two match there, but in that case you would not get the error message. You might want to use limits=>[0,20] anyway, since this might do a better job of telling when the two functions are correct to the proper precision (since otherwise it will be on [-2,2]).

Hope that does it for you.

Davide

In reply to Davide Cervone

Re: Checking submitted answers into a formula

by John Travis -
I knew it had to be something obvious to everyone else.  Glad to have stirred up Robin's question as well.  Thanks Davide.

JT
In reply to John Travis

Re: Checking submitted answers into a formula

by John Travis -
Now I want the student to enter two points in two different blanks and want the second to be different from the first...or three points in three blanks, etc.

Indeed, each of them is correct using the same ANS($f->cmp); but I want enforce the second point to not be the same as the first one.  Perhaps something like
#  for the first point
ANS($f->cmp);  
#  for the second point, what takes the place of $student_on_previous?
ANS($f->cmp(checker => sub {
      my ($correct,$student) = @_;
      return 0 unless ($correct == $student && $student <> $student_on_previous);
      Value->Error("Your two points must be different.") if $student == $student_on_previous;
return 1; }));

In reply to John Travis

Re: Checking submitted answers into a formula

by Davide Cervone -
Robin is right, the MultiAnswer object is what you want, here. I don't really care for the example from the wiki, however, as the use of ref is not really he right way to do the type checking.

Here is my example of the problem that you requested (where three points are required):

    DOCUMENT();

    loadMacros(
      "PGstandard.pl",
      "MathObjects.pl",
      "parserMultiAnswer.pl",
    );
    
    Context("Point");
    Parser::BOP::equality->Allow;     # Allow equalities in formulas
    
    $f = Compute("x^2 + y^2 = 1");    # The equation the students must match
    
    #
    #  The is the MultiAnswer object, with three correct points
    #  (only used for showing the correct answer).  It allows 
    #  blank answers, so students can add one point at a
    #  time, and it checks that no two points are equal.
    #
    $ma = MultiAnswer("(1,0)","(0,1)","(-1,0)")->with(
      function => $f,
      allowBlankAnswers => 1,
      checker => sub {
         my ($correct,$student,$self) = @_;
         my @student = @$student;    # The array of student answers
         my $n = scalar(@student);   # How many are in the array
         my @correct = (0) x $n;     # An array of that many zeros (assume none correct)
         foreach my $i (0..$n-1) {   # Loop through the student answers
            next unless $student[$i]->classMatch("Point");  # Go on if the answer is blank
            my ($x,$y) = $student[$i]->value;               # Get the x and y coordinates
            if ($self->{function}->eval(x=>$x,y=>$y)) {     # If the equation is satisfied (1 if equal, 0 if not)
              $correct[$i] = 1;                             # Indicate answer is correct
              foreach my $j (0..$i-1) {                     # Look through previous points
                if ($student[$j] == $student[$i]) {         # If this point equals a previous one
                  $self->setMessage($i+1,"This point is the same as the ".Value->NameForNumber($j+1)." one");
                  $correct[$i] = 0;                         # Give a warning and mark incorrect
                  break;                                    # Stop looking through previous points
                }
              }
            }
         }
         return @correct;    # Return the array indicating which are correct
      }
    );
    
    Context()->texStrings;
    BEGIN_TEXT
    Three distinct points that lie on \($f\) are:$BR
    \{$ma->ans_rule(15)\}, \{$ma->ans_rule(15)\}, and \{$ma->ans_rule(15)\}
    END_TEXT
    Context()->normalStrings;
    
    ANS($ma->cmp);
    
    ENDDOCUMENT();
Note that this checker works for any number of points. The checker itself could be packaged into a separate macro file for re-use, if so desired. For example, the macro file could contain
    $points_on_function = sub {
      my ($correct,$student,$self) = @_;
      ...
      return @correct;
    };
then you could use it as
    MultiAnswer("(1,0)","(0,1"),"(-1,0)")->with(
      function => $f,
      allowBlankAnswers => 1,
      checker => $points_on_function,
    );
In fact, in that case, you could do something like
    sub PointsOnFunction {
      my $f = shift;
      return MultiAnswer(@_)->with(
         function => Compute($f),
         allowBlankAnswers => 1,
         checker => $points_on_function,
      );
    }
in the macro file, and then just do
    PointsOnFunction("x^2+y^2=1","(1,0)","(0,1)","(-1,0)");
in the main program. Note that none of the approaches above checks that the correct answers are actually on the function given, so we assume you have them right. :-)

Hope that does the trick.

Davide