WeBWorK Problems

MathObjects and change of variables

MathObjects and change of variables

by Chrissy Safranski -
Number of replies: 9
I've been trying to write u-substitution problems where I ask for intermediate steps, like what u equals, what the integral turns into in terms of u, what it is after integrating in terms of u, and then finally the answer back in terms of x. 

There are a few problems like this in the OPL already, but they don't use MathObjects, instead using something like ANS(fcn_ans("correct expression", vars=>'u'));  

I would like to do the same thing using MathObjects, because that's how I know how to customize answer hints.  But I was having trouble because one answer needs the variable x and the next needs the variable u, and I don't want students to be able to enter something like "2x*u/(2x)" and have it be marked correct if the answer should be "u".  So I need to have only one variable allowed at a time, but both allowed in the whole problem.

I tried changing the line Context()->variables->are(u=>"Real") right before the line defining the correct answer in terms of u, but that still allowed mixed variables to be marked correct.  I tried breaking it up so that each answer had its own TEXT section immediately followed by the answer checker, and that didn't seem to fix it either.  Was I just doing something wrong, like defining things in the wrong place, or is there something else I should try?

I would also like to give them a custom message if they enter "2x*u/(2x)" but it wasn't working, I think because with test values for x and u, that expression is equal to u.  Can I get around that somehow, or should I just forget all this and use the older answer checkers and not customize the message?  
In reply to Chrissy Safranski

Re: MathObjects and change of variables

by Alex Jordan -
You can make separate contexts for these objects. Something like:

Context("Numeric")->variables->are(x=>'Real');
$fx = Formula("2x sin(x^2)");

Context("Numeric")->variables->are(u=>'Real');
$fu = Formula("sin(u)");

I used to think that there was a context called "Numeric" and you could switch in and out of it, possibly altering it along the way. But it's more like each time you use Context("..."); you create a totally new (very complicated) hash that has initial keys and values which can be altered. Here $fx would "know" that it came from the first of the two contexts, and point to the first hash that was created when it is time to do anything with $fx.

I'm probably misunderstanding something still, and Davide can correct me. But that's my current understanding of how things work and it's worked out for me for a while.
In reply to Chrissy Safranski

Re: MathObjects and change of variables

by Paul Pearson -
Hi Chrissy,

An example of this is used on the following wiki page:

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

Basically, you want to reset the variables in the context using ->variables->are() part way through the problem, following the general outline:

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

# Part 1
# Notice that x is not in the context, and \int \sin(2x) \, dx is written without using MathObjects (so that the variable x does not need to be added to the context).

Context('Numeric')->variables->are(u=>'Real', du=>'Real');
$ans1 = Compute("sin(u) * du/2");
BEGIN_TEXT
The integrand of \( \int \sin(2x) \, dx \) can be written as a function of \(u\).  What is the integrand in terms of the variable \(u\)? \{ ans_rule(20) \}
END_TEXT
ANS($ans1->cmp);

# Part 2
# Notice that u is not in the context
Context('Numeric')->variables->are(x=>'Real');
$ans2 = FormulaUpToConstant("-cos(2x)/2+C");
BEGIN_TEXT
What is the value of the integral in terms of the variable \(x\)? \{ ans_rule(20) \}
END_TEXT
ANS($ans2->cmp);

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

Best regards,

Paul Pearson
In reply to Paul Pearson

Re: MathObjects and change of variables

by Alex Jordan -
I think it's important to understand that the second
Context('Numeric')->...

has to be that way, and not just
Context()->...

or else you would only be changing the variables in the first context hash, and make $ans1 invalid. And when you do use a second
Context('Numeric')->...

you want to understand that any context modifications made prior to this will not still be in place (or rather they will, but only apply to objects that had been created in that first context). Now with the second Context('Numeric')->.., the active context has been reset to something new, a copy of the default.
In reply to Alex Jordan

Re: MathObjects and change of variables

by Chrissy Safranski -
Thank you to both of you!  Yes, I was using Context()->variables->... after the first (and only) time of defining Context("Numeric");  not a new Context("Numeric")->variables->...  So that should fix that.  

Thank you!
In reply to Chrissy Safranski

Re: MathObjects and change of variables

by Davide Cervone -
Alex and Paul have already given you one approach to this, which involves loading the Numeric context twice and setting different variables in each copy. Alex is correct that each invocation of Context("Numeric") creates a copy of the Numeric context and changes you make to the new copy will not affect the old copy, so you can get two versions of Numeric, one with the variable x and one with the variable u. Since MathObjects retain the context in which they were created, you can use multiple contexts within the problem as they have suggested.

The reason your original attempts failed is that the answer checking isn't performed until after the problem is completely laid out, and so the context's final state is the one used for any answers created within that context. It doesn't matter if those changes occurred after the ANS() call, or the BEGIN_TEXT/END_TEXT block where the answers were requested. The only thing that matters is the state of the context at the end of the problem. That is why you need two separate copies of the Numeric context to do what you asked.

There is another alternative, however, which would be to use one context containing both variables and either a custom checker or answer hints to provide an error message if the student uses a formula with x's. For example,

loadMacros("answerHints.pl");

Context("Numeric");
Context()->variables->are(u=>"Real",x=>"Real");

ANS(Compute("sin(u)")->cmp->withPostFilter(AnswerHints(
  sub {
    my ($correct,$student,$ans) = @_;
    return Value::isFormula($student) && $student->{variables}{x};
  } => [
    "Your formula should not include the variable 'x'",
    checkCorrect => 1, score => 0
  ]
)));
This checks the answer after the fact to see if the formula included x's and produces a message if it did. Note that this message will occur for all formulas that use x, not just the correct one. If you want to hint only when the answer is numerically correct but uses x's like your example, then use
loadMacros("answerHints.pl");

Context("Numeric");
Context()->variables->are(u=>"Real",x=>"Real");

ANS(Compute("sin(u)")->cmp->withPostFilter(AnswerHints(
  sub {
    my ($correct,$student,$ans) = @_;
    return $ans->{score} == 1 && Value::isFormula($student) && $student->{variables}{x};
  } => [
    "Your formula should not include the variable 'x'",
    checkCorrect => 1, score => 0
  ]
)));
This will mark incorrect formulas that contain x's as incorrect with no other message, whereas the first example above will show the message about not using x.

If you go with the two-context approach (each with a single variable), then if a student enters 2x*u/(2x), he or she will get a message like "Variable 'x' is not defined in this context", which may be confusing since x certainly has meaning in the problem. You can use answer hints to fix that, but it is easier to use use the MathObject error-message rewriting facility to customize the message. Something like

Context("Numeric");
Context()->variables->are(u=>"Real");
Context()->{error}{msg}{"Variable 'x' is not defined in this context"} = 
  "Your formula should not include the variable 'x'";
would do the trick.

Hope that gives you something useful to work with.

In reply to Davide Cervone

Re: MathObjects and change of variables

by Chrissy Safranski -
That's perfect, thank you!  I didn't realize that $student->{variables}{x} existed, so I was trying to use the trick of checking whether the derivative with respect to x was nonzero, and that worked for answers including x which didn't cancel, but not ones where the x cancels out, which is really where I wanted the message.  What you've given me will do exactly what I was envisioning.  

Thank you!
In reply to Chrissy Safranski

Re: MathObjects and change of variables

by Davide Cervone -
> I didn't realize that $student->{variables}{x} existed

One of the many undocumented features in MathObjects...

Glad you are able to use the example to do what you want.
In reply to Davide Cervone

Re: MathObjects and change of variables

by Chrissy Safranski -
I've now gotten around to actually writing problems using this, and I'm mostly happy with what I've got, but I have a couple more questions about error message customization in this setting.  I'm using FormulaUpToConstant for the answer to an indefinite integral, and answerHints subroutines to customize the error message for using wrong variables at the wrong times.

The biggest question I have is that when I type the answer without +C, it is marked incorrect with the message "Your answer is not the most general solution", but then when I correct it and add the +C at the end, it is marked correct (so far so good) with the message "This answer is equivalent to one you just submitted" (not good).  I would like to suppress this message, as the two answers are not equivalent, and one is wrong and the other is right, but I don't know how.

My second question is not as big a deal since I think the usual message is pretty clear anyway, but I was also trying to change the message "Your answer is not the most general solution" and couldn't get it to change no matter what I did.  So now I'm wondering what I'm missing.  I tried adding an answerHint for it, and I also tried changing the message in the Context.  Then I found in the documentation that there's actually a different Context with FormulaUpToConstant so I tried accessing that (see below), but it still didn't result in a changed error message, just the same one. I've tried with and without replaceMessage=>1, with FormulaUpToConstant instead of Compute, and no matter what I do I can't get a changed message.  I can't even get a different message with a test answer like "5u", which seems odd.  Something's going on that I don't understand.  

As I said, my first question is the one I really care about, but the second would be nice to know, too!

----------------------------
Context("Numeric");
Context()->variables->are(u=>"Real",x=>"Real",dx=>"Real",du=>"Real");

$intg=FormulaUpToConstant("$a/($b*$d)*2/3*u^(3/2)+C");
$ans=FormulaUpToConstant("$a/($b*$d)*2/3*($b*x^$d-$c)^(3/2)+C");

Context($intg->{context})->{error}{msg}{"Your answer is not the most general solution"} = 
  "Did you forget the +C?";
Context($ans->{context})->{error}{msg}{"Your answer is not the most general solution"} = 
  "Did you forget the +C?";

(...skipping a bunch of stuff that seems to be working just fine...)

ANS($intg->cmp->withPostFilter(AnswerHints(
  sub {
    my ($correct,$student,$ans) = @_;
    return Value::isFormula($student) && ($student->{variables}{x} || $student->{variables}{dx} || $student->{variables}{du});
  } => [
    "This is after integrating with respect to 'u', so your answer here should be in terms of 'u' and should not include 'du' (or 'x' or 'dx').",
    checkCorrect => 1, score => 0
  ], 
 sub {
    my ($correct,$student,$ans) = @_;
    return Value::isFormula($student) && not($student->{variables}{u});
  } => [
    "Your answer needs to include 'u' in it.",
    checkCorrect => 1, score => 0
  ],
 Compute("$a/($b*$d)*2/3*u^(3/2)")=>["Did you forget the +C?",checkCorrect => 1, score => 0,replaceMessage=>1],
FormulaUpToConstant("5u")=>["Test Message.", checkCorrect => 1, score => 0, replaceMessage=>1],
)));
-----------------------------
In reply to Chrissy Safranski

Re: MathObjects and change of variables

by Chrissy Safranski -
I kept trying and found a workaround for my chief question, (last line of my code below), but it seems to me like something that ought to be fixed with the FormulaUpToConstant answer checker.  

I also figured out that I could get my "Test Message" when I typed 5u+C, which makes sense, because FormulaUpToConstant adds the +C if you don't type it.  I tried using a subroutine instead, and got an error because it was trying to compare a formula with a FormulaUpToConstant, which also makes sense.  Ideally, I'd be able to check if the only difference between the student answer and the correct answer was the +C, but the FormulaUpToConstant answer checker seems to check for +C first, and I don't know enough about it to mess with it any more.  

Of course, if students are persistent and observant, the checker already tells them that.  From my experimentation, it seems that if the submitted answer is wrong and without a +C then the message is "Note: there is always more than one possibility." However, if their submitted answer is correct except for no +C then the message is "Your answer is not the most general solution."

-----------------------------------
ANS($intg->cmp(limits=>[9,10],showLinearityHints => 0)-> withPostFilter(AnswerHints(
 sub {
    my ($correct,$student,$ans) = @_;
    return Value::isFormula($student) && ($student->{variables}{x} || $student->{variables}{dx} || $student->{variables}{du});
  } => [
    "This is after integrating with respect to 'u', so your answer here should be in terms of 'u' and should not include 'du' (or 'x' or 'dx').",
    checkCorrect => 1, score => 0
  ], 
 sub {
    my ($correct,$student,$ans) = @_;
    return Value::isFormula($student) && not($student->{variables}{u});
  } => [
    "Your answer needs to include 'u' in it.",
    checkCorrect => 1, score => 0
  ],
 $intg=>["", checkCorrect => 1, score => 1, replaceMessage=>1],
)));

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