Update of /cvsroot/aimmath/AIM/WEB-INF/maple/aim
In directory sc8-pr-cvs1:/tmp/cvs-serv31913/aim
Added Files:
Tag: develop_2_1
Trig.mpl Util1.mpl
Log Message:
Added two new package files mainly for use with trigonometry problems. - GG
--- NEW FILE: Trig.mpl ---
# @(#)$Id: Trig.mpl,v 1.1.2.1 2003/07/09 09:59:48 gregg0 Exp $
# Copyright (C) 2003 Greg Gamble
# Distributed without warranty under the GPL - see README for details
read("Package.mpl"):
Package("aim/Trig","
This package provides some useful functions for use in
trigonometry questions. Look in the ROOT/examples/Trig
directory for some example quiz files demonstrating the
functions. The README in that directory has more details.
"
):
######################################################################
`Package/Assign`(
`type/aim/Trig/Problem`::boolean,
"A trigonometry problem is defined to be a list of form
@[eq, interval, solns]@ where
<dl>
<dt>@eq@</dt>
<dd>is an equation (the trigonometric equation
the students are asked to solve),</dd>
<dt>@interval@</dt>
<dd>is a Maple range, e.g. @-Pi/2 .. Pi/2@, that defines the
interval over which @eq@ is to be solved, and</dd>
<dt>@solns@</dt>
<dd>is the list of (correct) solutions of @eq@ in @interval@
(the case for #`aim/Trig/Test`#)
or, in order, the smallest, largest and number of solutions
of @eq@ in @interval@
(the case for #`aim/TrigSLN/Test`#)</dd>
</dl>",
proc(e)
evalb(type(e, list) and
nops(e) = 3 and
type(e[1], equation) and
type(e[2], range) and
type(e[3], list) and
{op(remove(type, e[3], realcons))} subset {EMPTYCELL});
end
):
######################################################################
`Package/Assign`(
`aim/Trig/Test`::[numeric, string, string, list, list],
"This function assumes that the student was asked to do a
trigonometry question @Q@ where the expected answer is all the solutions
of an equation on a certain interval. The first argument @a@ might be an
#`aim/Question/Attempt`# or #`aim/Question/ShortAttempt`# object
(in which case we put @ans := a['Answer']@) or it might just be
the student's answer (in which case we put @ans = a@).
In any case, the function checks whether @ans@ is correct.
If not, it checks that @ans@ contains the right number of solutions
and awards a mark according to the number of correct solutions,
essentially @(ncorrect - nwrong)/nrightans@, where @ncorrect@ and
@nwrong@ are the numbers of correct and wrong solutions in @ans@,
respectively, and @nrightans@ is the number of solutions in the
right answer. A solution is deemed correct if it is accurate to
2 decimal places. Solutions that are inaccurate but within 5%
also score, but at the rate of 80% that of a fully correct
answer. Wrong or inaccurate answers generate feedback, identifying
which of the solutions are incorrect.
The function returns a list of the form
@[mark, feedback, note, [wrong], [partright]]@. If
@a@ is an #`aim/Question/Attempt`# then the function also sets
@a['RawMark']@ to @mark@, appends @feedback@ to @a['Feedback']@,
and @note@ to @a['Note']@.
",
proc(a, Q::`aim/Trig/Problem`)
local i, j, ans, argtype, rightans, wrong, mark, partright,
eq, interval, llim, rlim, feedback, note;
if type(a,`aim/Question/Attempt`) then
argtype := "attempt";
ans := a['Answer'];
elif type(a,`aim/Question/ShortAttempt`) then
argtype := "shortattempt";
ans := a['Answer'];
else
argtype := "ordinary";
ans := eval(a);
fi;
ans := sort([op(ans)], `<` @ evalf);
eq, interval, rightans := op(Q);
llim, rlim := op(interval);
feedback := NULL;
note := "";
if is(ans[1] < llim - 0.01 or ans[-1] > rlim + 0.01) then
feedback :=
feedback,
sprintf(
__("Solutions should lie in the interval $(%s, %s)$.\\\\"),
TextStyle(LaTeX(llim)), TextStyle(LaTeX(rlim)) );
note := __("Solutions do not all lie in required interval");
if is(ans[1] < -2 * Pi - 0.01 or ans[-1] > 2 * Pi + 0.01) then
feedback :=
feedback,
__("Perhaps you attempted to give answers in degrees;
note that answers should be in radians.\\\\");
note := __("Solutions in degrees?");
fi;
fi;
j := 1;
mark := 0;
wrong := NULL;
partright := NULL;
for i to nops(ans) do
while j < nops(rightans) and is(op(j, rightans) < op(i, ans)) and
not `aim/TestNumeric`(op(j, rightans), op(i, ans), 'margin' = 0.02) do
j := j + 1;
od;
if j > nops(rightans) then
wrong := wrong, i;
elif type(op(j, rightans), Not(float)) then
# exact answer only accepted
if `aim/TestEqual`(op(j, rightans), op(i, ans)) then
mark := mark + 1/nops(rightans);
j := j + 1;
else
wrong := wrong, i;
fi;
elif `aim/TestNumeric`(op(j, rightans), op(i, ans), 'dpround' = -2) then
mark := mark + 1/nops(rightans);
j := j + 1;
elif `aim/TestNumeric`(op(j, rightans), op(i, ans), 'margin' = 0.02) then
if i < nops(ans) and is(op(i, ans) < op(j, rightans)) and
is(op(i + 1, ans) <= op(j, rightans)) then
wrong := wrong, i;
else
partright := partright, i;
mark := mark + 0.8/nops(rightans);
j := j + 1;
fi;
else
wrong := wrong, i;
fi;
od;
if mark = 0 then
feedback :=
feedback,
__("None of the solutions you have given is correct.\\\\");
if note = "" then
note := __("No correct solutions");
fi;
elif nops(ans) > nops(rightans) then
# penalise for too many solutions
mark := max(0, mark - (nops(ans) - nops(rightans))/nops(rightans));
if note = "" then
note := __("At least one extraneous solution");
fi;
if nops([wrong]) = 1 then
feedback :=
feedback,
__("You gave an extra solution which is not correct.\\\\");
else
feedback :=
feedback,
__("You gave some extra solutions which are not correct.\\\\");
fi;
elif nops([partright]) > 0 and note = "" then
note := __("At least one inaccurate solution");
fi;
if mark = 0 then
; #already dealt with
elif mark < 1 then
if nops(ans) <> nops(rightans) then
if note = "" then
note := __("Insufficient number of solutions");
fi;
if nops(rightans) = 1 then
feedback :=
feedback, __("Note that, in all, there is only one solution.\\\\");
else
feedback :=
feedback,
sprintf(
__("Note that, in all, there are %d (distinct) solutions.\\\\"),
nops(rightans) );
fi;
fi;
if nops([wrong]) + nops([partright]) > 0 then
feedback :=
feedback,
__("Ordering your solutions from smallest to largest \\dots\\\\");
fi;
if nops([wrong]) = 1 then
feedback :=
feedback,
sprintf( __("Your %s solution is incorrect.\\\\"), nth(wrong) );
elif nops([wrong]) > 1 then
feedback :=
feedback,
sprintf( __("Your %s"), nth(wrong[1]) );
for i from 2 to nops([wrong]) - 1 do
feedback :=
feedback,
sprintf( __(", %s"), nth(wrong[i]) );
od;
feedback :=
feedback,
sprintf( __(" and %s solutions are incorrect.\\\\"), nth(wrong[-1]) );
fi;
if nops([partright]) = 1 then
feedback :=
feedback,
sprintf(
__("Your %s solution is nearly correct
(but has insufficient accuracy).\\\\"),
nth(partright) );
elif nops([partright]) > 1 then
feedback :=
feedback,
sprintf( __("Your %s"), nth(partright[1]) );
for i from 2 to nops([partright]) - 1 do
feedback :=
feedback,
sprintf( __(", %s"), nth(partright[i]) );
od;
feedback :=
feedback,
sprintf(
__(" and %s solutions are nearly correct
(but do not have sufficient accuracy).\\\\"),
nth(partright[-1]) );
fi;
fi;
RETURN([mark, cat("<latex>\n", feedback, "</latex>\n"), note,
[wrong], [partright]]);
end
):
######################################################################
`Package/Assign`(
`aim/TrigSLN/Test`::[numeric, string, string, list, list],
"This function assumes that the student was asked to do a
trigonometry question @Q@ where the expected answer is the smallest,
largest and number of solutions of an equation on a certain interval.
The first argument @a@ might be an
#`aim/Question/Attempt`# or #`aim/Question/ShortAttempt`# object
(in which case we put @ans := a['Answer']@) or it might just be
the student's answer (in which case we put @ans = a@).
In any case, the function checks whether @ans@ is correct.
If not, it checks @1/3@ is awarded for each accurate solution;
@1/3@ is also awarded for giving the correct number of solutions.
A solution is deemed correct if it is accurate to 2 decimal places.
Solutions that are inaccurate but within 5% also score, but at the
rate of 80% that of a fully correct answer. Wrong or inaccurate answers
generate feedback, identifying which of the solutions are incorrect.
The function returns a list of the form
@[mark, feedback, note, [wrong], [partright]]@. If
@a@ is an #`aim/Question/Attempt`# then the function also sets
@a['RawMark']@ to @mark@, appends @feedback@ to @a['Feedback']@,
and @note@ to @a['Note']@.
",
proc(a, Q::`aim/Trig/Problem`)
local i, j, allans, ans, argtype, rightans, wrong, mark, partright,
eq, interval, llim, rlim, feedback, note;
if type(a,`aim/Question/Attempt`) then
argtype := "attempt";
ans := a['Answer'];
elif type(a,`aim/Question/ShortAttempt`) then
argtype := "shortattempt";
ans := a['Answer'];
else
argtype := "ordinary";
ans := eval(a);
fi;
eq, interval, rightans := op(Q);
llim, rlim := op(interval);
feedback := NULL;
note := "";
allans := ans;
ans := select(type, [op(1 .. 2, ans)], realcons);
if nops(ans) = 2 and is(ans[1] > ans[2]) then
ans[1], ans[2] := ans[2], ans[1];
feedback :=
feedback,
__("Your solutions were given in the wrong order.\\\\");
note := __("Solutions in wrong order");
fi;
if nops(ans) > 0 and is(ans[1] < llim - 0.01 or ans[-1] > rlim + 0.01) then
feedback :=
feedback,
sprintf(
__("Solutions should lie in the interval $(%s, %s)$.\\\\"),
TextStyle(LaTeX(llim)), TextStyle(LaTeX(rlim)) );
note := __("Solutions do not all lie in required interval");
if is(ans[1] < -2 * Pi - 0.01 or ans[-1] > 2 * Pi + 0.01) then
feedback :=
feedback,
__("Perhaps you attempted to give answers in degrees;
note that answers should be in radians.\\\\");
note := __("Solutions in degrees?");
fi;
fi;
j := 1;
mark := 0;
wrong := NULL;
partright := NULL;
for i to nops(ans) do
while j < 2 and op(j, rightans) < op(i, ans) and
not `aim/TestNumeric`(op(j, rightans), op(i, ans), 'margin' = 0.02) do
j := j + 1;
od;
if j > 2 then
wrong := wrong, i;
elif type(op(j, rightans), Not(float)) then
# exact answer only accepted
if `aim/TestEqual`(op(j, rightans), op(i, ans)) then
mark := mark + 1/3;
j := j + 1;
else
wrong := wrong, i;
fi;
elif `aim/TestNumeric`(op(j, rightans), op(i, ans), 'dpround' = -2) then
mark := mark + 1/3;
j := j + 1;
elif `aim/TestNumeric`(op(j, rightans), op(i, ans), 'margin' = 0.02) then
if i < nops(ans) and is(op(i, ans) < op(j, rightans)) and
is(op(i + 1, ans) <= op(j, rightans)) then
wrong := wrong, i;
else
if i = 1 and j = 2 then
feedback :=
feedback,
__("You have missed a smaller solution.\\\\");
note := __("Missed a smaller solution");
fi;
partright := partright, i;
mark := mark + 0.8/3;
j := j + 1;
fi;
else
wrong := wrong, i;
fi;
od;
if mark = 0 then
feedback :=
feedback,
__("You gave no correct solutions.\\\\");
note := __("No correct solutions");
elif mark < 2/3 then
if nops([wrong]) = 1 and nops(ans) = 2 then
feedback :=
feedback,
sprintf(
__("Your %s solution is incorrect.\\\\"),
`if`(evalb(wrong = 1), "smaller", "larger") );
fi;
if nops([partright]) = 1 then
if nops(ans) = 1 then
feedback :=
feedback,
__("The solution you have given is nearly correct
(but has insufficient accuracy).\\\\");
else
feedback :=
feedback,
sprintf(
__("Your %s solution is nearly correct
(but has insufficient accuracy).\\\\"),
`if`(evalb(partright = 1), "smaller", "larger") );
fi;
elif nops([partright]) = 2 then
feedback :=
feedback,
__("Both your solutions are nearly correct
(but have insufficient accuracy).\\\\");
fi;
fi;
ans := allans;
if op(3, rightans) = op(3, ans) then
mark := mark + 1/3;
else
feedback :=
feedback,
__("You have not given the (correct) number of solutions.\\\\");
if note = "" then
note := __("Incorrect number of solutions");
fi;
wrong := wrong, 3;
fi;
if mark = 0 then
note := __("Totally incorrect answer");
elif nops([partright]) > 0 and note = "" then
note := __("At least one inaccurate solution");
fi;
RETURN([mark, cat("<latex>\n", feedback, "</latex>\n"), note,
[wrong], [partright]]);
end
):
######################################################################
EndPackage():
--- NEW FILE: Util1.mpl ---
# @(#)$Id: Util1.mpl,v 1.1.2.1 2003/07/09 09:59:48 gregg0 Exp $
# Copyright (C) 2003 Greg Gamble
# Distributed without warranty under the GPL - see README for details
("Package.mpl"):
Package("aim/Util1","
This package provides some extra utility functions initially created
as they were needed by or were useful with the #`aim/Trig`# package.
"
):
######################################################################
`Package/Assign`(
decplaces::float,
"Returns @x@ rounded to @n@ decimal places",
proc(x::realcons, n::integer)
round(x * 10^n) * 0.1^n;
end
):
`Package/Assign`(
`SimplestDecimal`::float,
"Returns @x@ to the least number of decimal places required to represent
@x@ accurately. The optional argument @'maxdp' = d@ or
@'maxsf' = s@ may be included, where @d, s@ are integers, in which
case at most @d@ decimal places or @s@ significant digits are included
in the returned result, rounding if necessary.",
proc(x::realcons)
local d;
d := Digits;
if nargs > 1 then
if op(1, args[2]) = 'maxsf' then
d := op(2, args[2]);
elif op(1, args[2]) = 'maxdp' then
d := op(2, args[2]);
if floor(abs(x)) <> 0 then
d := d + floor( log[10]( abs(x) ) ) + 1;
fi;
fi;
fi;
while d > 1 and evalf[d](x) = evalf[d - 1](x) do
d := d - 1;
od;
return evalf[d](x);
end
):
`Package/Assign`(
Exists::boolean,
"exists quantifier, returns the result of: exists x in @lis@, @func@(x)
where @func@ is @f@ if @f@ has type procedure or is defined by
@func@ := x -> type(x, @f@)",
proc(lis::{list,set,string}, f)
local x, func;
func := `if`(type(f, procedure), f, x -> type(x, f));
for x in lis do
if func(x) then
return true;
fi;
od;
return false;
end
):
`Package/Assign`(
Forall::boolean,
"forall quantifier, returns the result of: for all x in @lis@, @func@(x)
where @func@ is @f@ if @f@ has type procedure or is defined by
@func@ := x -> type(x, @f@)",
proc(lis::{list,set,string}, f)
local func;
func := `if`(type(f, procedure), f, x -> type(x, f));
not Exists(lis, x -> not func(x));
end
):
`Package/Assign`(
In::boolean,
"Returns @true@ if @x@ is in the interval [@llim@, @rlim@], or
@false@ otherwise",
proc(x::realcons, llim::realcons, rlim::realcons)
is(x >= llim and x <= rlim);
end
):
`Package/Assign`(
nth::string,
"Returns the ordinal corresponding to @n@, e.g. @nth(3)@ returns 3rd",
proc(n::integer)
local lasttwo, lastdigit;
if n < 0 then
cat(n, "th");
else
lasttwo := irem(n, 100);
lastdigit := irem(n, 10);
if lastdigit = 1 and lasttwo <> 11 then
cat(n, "st");
elif lastdigit = 2 and lasttwo <> 12 then
cat(n, "nd");
elif lastdigit = 3 and lasttwo <> 13 then
cat(n, "rd");
else
cat(n, "th");
fi;
fi;
end
):
`Package/Assign`(
TextStyle::string,
"Returns a LaTeX textstyle version of a displaystyle LaTeX string.
At the moment it only changes \"\\frac{..}{..}\" to \"{..}/{..}\"",
proc(s::string)
local s_, pos, posb, posn, bcount;
s_ := s;
do
pos := SearchText("\\frac", s_);
if pos = 0 then
return s_;
fi;
posb := pos + 5;
while posb < length(s_) and s_[posb] = " " do
posb := posb + 1;
od;
posn := posb; #position of start of numerator
bcount := 0;
if s_[posb] = "{" then
bcount := bcount + 1;
posb := posb + 1;
fi;
while bcount > 0 and posb < length(s_) do
if s_[posb] = "{" then
bcount := bcount + 1;
elif s_[posb] = "}" then
bcount := bcount - 1;
fi;
posb := posb + 1;
od;
if posb >= length(s_) or bcount < 0 then
# s_ doesn't have a well-formed \frac{..}{..} ... just return
return s_;
fi;
s_ := cat(s_[1 .. pos - 1], s_[posn .. posb - 1], "/", s_[posb .. -1]);
od;
end
):
######################################################################
EndPackage():
|