forked from openwebwork/pg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanswerComposition.pl
238 lines (188 loc) · 7.67 KB
/
answerComposition.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
################################################################################
# WeBWorK Online Homework Delivery System
# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
# $CVSHeader: pg/macros/answerComposition.pl,v 1.8 2009/06/25 23:28:44 gage Exp $
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of either: (a) the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version, or (b) the "Artistic License" which comes with this package.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
# Artistic License for more details.
################################################################################
=head1 NAME
answerComposition.pl - An answer checker that determines if two functions
compose to form a given function.
=head1 DESCRIPTION
answerComposition.pl provides an answer checker that determines if two functions
compose to form a given function. This can be used in problems where you ask a
student to break a given function into a composition of two simpler functions,
neither of which is allowed to be the identity function.
=cut
sub _answerComposition_init {}; # don't reload this file
=head1 MACROS
=head2 COMPOSITION_ANS
COMPOSITION_ANS($f, $g, %options)
An answer checked to see if $f composed with $g matches a given function,where
$f and $g are one possible decomposition of the target function, and options are
any of the options allowed by composition_ans_list() below.
$f and $g are used to display the "correct" answer, and the composition is
computed from them.
This function actually supplies TWO answer checkers, for the two previous answer
blanks. So be sure to call it immediately after the answer blanks have been
supplied. (It may be best to use the NAMED_COMPOSITION_ANS checker below, which
specifies the answer blanks explicitly.)
Example:
BEGIN_TEXT
\(f\circ g = (1+x)^2\) when
\(f(x)\) = \{ans_rule(20)\} and \(g(x)\) = \{ans_rule(20)\}
END_TEXT
COMPOSITION_ANS("x^2","1+x");
=cut
sub COMPOSITION_ANS {
my $f = shift; my $g = shift;
my $num_of_answers = main::ans_rule_count();
my $fID = ANS_NUM_TO_NAME($num_of_answers-1);
my $gID = ANS_NUM_TO_NAME($num_of_answers);
my %ans = composition_ans_list($fID=>$f,$gID=>$g,@_);
ANS($ans{$fID},$ans{$gID});
}
=head2 NAMED_COMPOSITION_ANS
NAMED_COMPOSITION_ANS($fID=>$f, $gID=>$g, %options)
An answer checked to see if $f composed with $g matches a given function, where
$fID and $gID are the names of the answer rules for the functions $f and $g, and
$f and $g are the answers for the functions. %options are any of the options
allowed by composition_ans_list() below.
This routine allows you to put the answer blanks for $f and $g at any location
in the problem, and in any order.
Example:
BEGIN_TEXT
\(g\circ f = (1+x)^2\) when
\(f(x)\) = \{NAMED_ANS('f',20)\} and \(g(x)\) = \{NAMED_ANS('g',20)\}
END_TEXT
NAMED_COMPOSITION_ANS(f => "x^2", g => "1+x");
=cut
sub NAMED_COMPOSITION_ANS {NAMED_ANS(composition_ans_list(@_))}
=head2 composition_ans_list
composition_ans_list($fID=>$f, $gID=>$g, %options)
This is an internal routine that returns the named answer checkers
used by COMPOSITION_ANS and NAMED_COMPOSITION_ANS above.
$fID and $gID are the names of the answer rules for the functions and $f and $g
are the answers for these functions. %options are from among:
=over
=item S<C<< var => 'x' >>>
the name of the variable to use when
both functions use the same one
=item S<C<< vars => ['x','t'] >>>
the names of the variables for $f and $g
=item S<C<< showVariableHints => 1 or 0 >>>
do/don't show errors when the variable
used by the student is incorrect
=back
=cut
sub composition_ans_list {
my ($fID,$f,$gID,$g,%params) = @_; my @IDs = ($fID,$gID);
#
# Get options
#
$params{vars} = [$params{var},$params{var}] if $params{var} && !$params{vars};
$params{showVariableHints} = 1 unless defined($params{showVariableHints});
my $isPreview = $main::inputs_ref->{previewAnswers};
my $vars = $params{vars} || [];
my @options = (ignoreInfinity=>0,ignoreStrings=>0);
my ($i,$error);
#
# Get correct answer data and determine which variables to use
#
$f = Value->Package("Formula")->new($f); $g = Value->Package("Formula")->new($g);
my %correct = ($fID => $f, $gID => $g);
my %x = ($fID => $vars->[0], $gID => $vars->[1]);
foreach $i (@IDs) {
unless ($x{$i}) {
die "Can't tell which variable to use for $correct{$i}: ".
"use var=>'x' or vars=>['x','y'] to specify it"
if scalar(keys %{$correct{$i}->{variables}}) > 1;
$x{$i} = (keys %{$correct{$i}->{variables}})[0];
}
die "$correct{$i} is not a function of $x{$i}"
unless defined($correct{$i}->{variables}{$x{$i}});
}
my %y = ($fID => $x{$gID}, $gID => $x{$fID});
my %ans = ($fID => message_cmp($f), $gID => message_cmp($g));
my $fog = $f->substitute($x{$fID}=>$g); # the composition
#
# Check that the student formulas parse OK,
# produce a number, contain the correct variable,
# don't contain the composition itself in a simple way,
# and aren't the identity.
#
my %student = ($fID => $main::inputs_ref->{$fID},
$gID => $main::inputs_ref->{$gID});
foreach $i (@IDs) {
next unless defined($student{$i});
$student{$i} = Parser::Formula($student{$i});
if (!defined($student{$i})) {$error = 1; next}
$ans{$i}->{rh_ans}{preview_latex_string} = $student{$i}->TeX;
if ($student{$i}->type ne 'Number') {
$ans{$i} = $correct{$i}->cmp(@options);
$error = 1; next;
}
if ($x{$fID} ne $x{$gID} && defined($student{$i}->{variables}{$y{$i}})) {
$ans{$i}->{rh_ans}{ans_message} = "Your formula may not contain $y{$i}"
unless $isPreview || !$params{showVariableHints};
$error = 1; next;
}
if (!defined($student{$i}->{variables}{$x{$i}})) {
$ans{$i}->{rh_ans}{ans_message} = "Your formula is not a function of $x{$i}"
unless $isPreview || !$params{showVariableHints};
$error = 1; next;
}
if (($student{$i}->{tree}->class eq 'BOP' &&
($fog == $student{$i}->{tree}{lop} || $fog == $student{$i}->{tree}{rop})) ||
($student{$i}->{tree}->class eq 'UOP' && $fog == $student{$i}->{tree}{op})) {
$ans{$i}->{rh_ans}{ans_message} =
"Your formula may not have the composition as one of its terms";
$error = 1; next;
}
if ($fog == $student{$i}) {
$ans{$i}->{rh_ans}{ans_message} =
"Your formula my not be the composition itself";
$error = 1; next;
}
if (Parser::Formula($x{$i}) == $student{$i}) {
$ans{$i}->{rh_ans}{ans_message} = "The identity function is not allowed"
unless $isPreview;
$error = 1; next;
}
}
#
# If no error, and both answers are given, check if compositions are equal
#
if (!$error && defined($student{$fID}) && defined($student{$gID})) {
if ($fog == $student{$fID}->substitute($x{$fID}=>$student{$gID})) {
$ans{$fID}->{rh_ans}{score} = $ans{$gID}->{rh_ans}{score} = 1;
}
}
return (%ans);
}
=head2 message_cmp
message_cmp($correct)
Returns an answer evaluator that always returns incorrect, with a given error
message. Used by COMPOSITION_ANS to produce "dummy" answer checkers for the two
parts of the composition.
=cut
sub message_cmp {
my $correct = shift;
my $answerEvaluator = new AnswerEvaluator;
$answerEvaluator->ans_hash(
type => "message",
correct_ans => $correct->string,
ans_message => $message,
);
$answerEvaluator->install_evaluator(sub {shift});
return $answerEvaluator;
}
1;