- $num_quote = Compute("42/9");
passed the string "42/9" to Compute(). Now $num_quote is a perl hash containing lots of information, including remembering that it was originally defined using the string "42/9". But it also knows that its numerical value is 4.66... (up to about 16 or 17 digits). Maybe something else will know to interpret that string as a mathematical fraction, but at this point $num_quote is not a fraction in any mathematical meaningful way. Now when $num_quote is used in some way where a string is needed, it can still provide "42/9" or it can provide "4.66..." (all digits). Or it will provide "4.66667" because the default context settings turn decimals into strings with only 6 digits of precision. It depends on how $num_quote is used. And take note that even if it provides "42/9", if you are passing that into something else (another Compute call, a Formula call, etc) then maybe that other thing will in turn cause 42/9 to be displayed as a decimal. - $num_noquo = Compute(42/9);
had perl first do the division, getting 4.66... (up to about 16 or 17 significant digits). The string version of that is what Compute() receives. There is no hope of anything downstream remembering it was "42/9". (Although some tools might still try to turn that truncated decimal into a nearby fraction with a small denominator.)
Thanks for the reply.
That $num_noquo is stuck as floating-point makes perfect sense -- it started as floating-point. I would expect (hope? prefer?) that $func_quote, $func_perl, and also Compute($num_quote*$func_sub) would retain an understanding that I "meant" 42/9 not 4.66...
I would like the fraction to remain for display purposes; do you know of a way to do this?
You distinguish between display accuracy and computational accuracy. Here's where this originally cropped up:
DOCUMENT();
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->variables->are(k1=>"Real", k2=>"Real", t=>"Real");
$y1 = Compute("e^(-4t)");
$y2 = Compute("e^(-2t)");
$yp = Compute("7/85 cos(t) + 6/85 sin(t)");
$y_gen = Compute("k1 $y1 + k2 $y2 + $yp");
$const_1 = Compute(2/17); # k1
$const_2 = Compute(-1/5); # k2
$y_spec = $y_gen->substitute(k1=>$const_1, k2=>$const_2); #decimal
#$y_spec = Compute("2/17 $y1 + -1/5 $y2 + $yp"); # decimal
#$y_spec = Compute("2/17 $y1 + -1/5 $y2 + 7/85 cos(t) + 6/85 sin(t)"); # fractions
$y_spec->{test_points} = [ [1,1,0.005] ]; # ok at 0.00920 or greater
#$y_spec->{limits} = [0.5,5];
BEGIN_TEXT
(a) Find the general solution of the differential equation
\[ \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}. \]
\(\quad\) \(y(t) = \) \{ans_rule(50)\}
$PAR \(\quad\) Use "k1" and "k2" for the constants in your solution.
$PAR (b) Find the solution of the initial-value problem
\[ \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}, \quad y(0)=y'(0)=0. \]
\(\quad\) \(y(t) = \) \{ans_rule(50)\}
END_TEXT
sub mycheck {
my ($correct, $student, $ansHash) = @_;
if ( $student->usesOneOf("k1") && # uses constant k1 -- not necessary?
$student->usesOneOf("k2") && # uses constant k2 -- not necessary?
( ($student->substitute(k1=>1, k2=>0) == $y1 + $yp) || # one of terms is y1
($student->substitute(k1=>0, k2=>1) == $y1 + $yp) ) &&
( ($student->substitute(k1=>1, k2=>0) == $y2 + $yp) || # one of terms is y2
($student->substitute(k1=>0, k2=>1) == $y2 + $yp) ) &&
$student->substitute(k1=>0, k2=>0) == $yp # probably redundant w/above...
)
{ return 1; } else { return 0; }
}
ANS($y_gen->cmp(checker=>~~&mycheck));
#ANS($y_spec->cmp);
ANS($y_spec->cmp(diagnostics=>1));
ENDDOCUMENT();
I have three versions of $y_spec. The first is what I'd like because it minimizes copy/paste errors on my part -- I need only get $yp correct in one place. If one enters the correct function
2/17 e^(-4t) - 1/5 e^(-2t) + 7/85 cos(t) + 6/85 sin(t)
and an evaluation point is unluckily chosen in the interval [-0.0092,0.0092] then the answer is marked incorrect b/c the relative error is greater than 0.001 (I checked the interval). The answer display in this first version uses six-digit decimals. That got me thinking maybe the decimal approximation is the issue. So I put the decimal approximation version and the fraction version into Matlab and plotted the relative error -- it's larger than 0.001 on the interval [-0.0141,0.0141]. That's not identical to webwork/perl but not outlandishly different either.
The second version of $y_spec exhibits the same behavior -- decimal display and marked incorrect.
The third version of $y_spec displays fractions and marks the answer correct -- the relative error from ANS($y_spec->cmp(diagnostics=>1)) is on the order of 10^-12.
So: is there something going on with keeping fewer digits than double-precision floating-point? Is this at all connected to the answer display?
Thanks in advance for your thoughts on this - Thomas
...and the Matlab plot showing large relative difference between a decimal approximation and the fraction version.
Alex (or anyone else!),
You point out that fractional or decimal depends on how it's used, and function calls may cause decimals instead of fractions -- is there a general rule for when it stays fractional and when it's converted to decimal?
Here's a simplified version of the issue I'm having:
How can I code something like
$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
to generate a "Correct Answer" that typesets as
42/19 cos(sqrt(3)x)
instead of
4.66667*cos(1.73205*x)
Doing Compute("$num_quote $func_sub->{string}") does maintain the sqrt(3) but I can't figure out anything that maintains the 42/19.
Same question for
Context()->variables->add(k=>"Real");
$const = Compute("31/2");
$func_const = Compute("k*sin(sqrt7x)");
$func_subbed = $func_const->substitute(k=>$const);
Hopefully that illuminates the inner workings for me, and helps me get a handle on both answer display and the weird numerical tolerance issue I described above.
Thanks! - Thomas
$num = Compute("42/9");
Paraphrasing from Davide Cervone about this: Compute() calls Function() on the string "42/9", after first setting the context flags reduceConstants, reduceConstantFunctions, and showExtraParens to 0. Then if the formula is constant (which it is in this case), the formula is evaluated to get a Real, which is already something with a floating point value. Then Formula() is once again called on the original string "42/9" (this time with the reductions turned off), and this is used to produce the string and TeX versions for the correct answer.
So $num is a Real. Its value method gives 4.666... Its string and TeX methods give 4.66667. And its correct_ans and correct_ans_latex_string properties (properties, not methods) are respectively '42/9' and '\frac{42}{9}'.
So the things you want (those fractions in the last two properties I mentioned) are just not what you get when you use $num as a string. Instead you get the result of the string method: 4.66667.
What you really want is either:
A. Explicitly make $num be a Formula() object, and use the context flag reduceConstants => 0. So:
Context()->flags->set(reduceConstants => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
B. Use the Fraction context, which under default settings is also going to reduce your "42/9" to "14/3":
loadMacros("contextFraction.pl");
Context("Fraction");
$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
However, both of these ways of doing it cause your "sqrt3" to be a decimal. This is because $func is the result of a Formula() times a Formula(), or a Fraction() times a Formula(). The result is a Formula() that did not get the special treatment that Compute() uses for setting those special string properties. So now what you probably want is to stop things like "sqrt(3)" from being executed. That's a function applied to a constant, and for that there is the flag with the slightly confusing name reduceConstantFunctions:
Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
loadMacros("contextFraction.pl");
Context("Fraction")->flags->set(reduceConstantFunctions => 0);
$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
DOCUMENT();
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
Context()->variables->add(k=>"Real");
$const = Formula("31/2");
$func_const = Compute("k*sin(sqrt7x)");
$func_subbed = $func_const->substitute(k=>$const);
BEGIN_PGML
1. Number: [____________]{$num}
1. Function: [____________]{$func}
1. Constant: [____________]{$func_const}
1. Subbed: [____________]{$func_subbed}
END_PGML
ENDDOCUMENT();
Alex,
This also fixed the numerical issue that led me down this rabbit hole!
Thanks! - Thomas
DOCUMENT();
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->variables->are(k1=>"Real", k2=>"Real", t=>"Real");
Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$y1 = Compute("e^(-4t)");
$y2 = Compute("e^(-2t)");
$yp = Compute("7/85 cos(t) + 6/85 sin(t)");
$y_gen = Compute("k1 $y1 + k2 $y2 + $yp");
$const_1 = Formula("2/17"); # k1
$const_2 = Formula("-1/5"); # k2
$y_spec = $y_gen->substitute(k1=>$const_1, k2=>$const_2);
$y_spec->{test_points} = [ [1,1,0.005] ];
$y_gen->cmp(diagnostics=>1,
checker => sub {
my ($correct, $student, $ansHash) = @_;
if ( ( ($student->substitute(k1=>1, k2=>0) == $y1 + $yp) || # one of terms is y1
($student->substitute(k1=>0, k2=>1) == $y1 + $yp) ) &&
( ($student->substitute(k1=>1, k2=>0) == $y2 + $yp) || # one of terms is y2
($student->substitute(k1=>0, k2=>1) == $y2 + $yp) ) #&&
)
{ return 1; } else { return 0; }
}
);
BEGIN_PGML
(a) Find the general solution of the differential equation
[`\displaystyle \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}. `]
[`y(t) = `] [______________________________________________________]{$y_gen}
Use "k1" and "k2" for the constants in your solution.
(b) Find the solution of the initial-value problem
[`\displaystyle \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}, \quad y(0)=y'(0)=0. `]
[`y(t) = `] [_______________________________________________________]{$y_spec}
END_PGML ENDDOCUMENT();