| 1 | BEGIN{ |
1 | BEGIN{ |
| 2 | be_strict(); |
2 | be_strict(); |
| 3 | } |
3 | } |
| 4 | |
4 | |
| 5 | sub _PGmorematrixmacros_init{} |
5 | sub _PGmorematrixmacros_init{} |
| 6 | |
6 | |
| 7 | sub random_inv_matrix { ## Builds and returns a random invertible \$row by \$col matrix. |
7 | sub random_inv_matrix { ## Builds and returns a random invertible \$row by \$col matrix. |
| … | |
… | |
| 29 | This method returns a random nxn diagonal matrix. |
29 | This method returns a random nxn diagonal matrix. |
| 30 | |
30 | |
| 31 | =cut |
31 | =cut |
| 32 | |
32 | |
| 33 | sub random_diag_matrix{ ## Builds and returns a random diagonal \$n by \$n matrix |
33 | sub random_diag_matrix{ ## Builds and returns a random diagonal \$n by \$n matrix |
| 34 | |
34 | |
| 35 | warn "Usage: \$new_matrix = random_diag_matrix(\$n)" if (@_ != 1); |
35 | warn "Usage: \$new_matrix = random_diag_matrix(\$n)" if (@_ != 1); |
| 36 | |
36 | |
| 37 | my $D = new Matrix($_[0],$_[0]); |
37 | my $D = new Matrix($_[0],$_[0]); |
| 38 | my $norm = 0; |
38 | my $norm = 0; |
| 39 | while( $norm == 0 ){ |
39 | while( $norm == 0 ){ |
| 40 | foreach my $i (1..$_[0]){ |
40 | foreach my $i (1..$_[0]){ |
| 41 | foreach my $j (1..$_[0]){ |
41 | foreach my $j (1..$_[0]){ |
| 42 | if( $i != $j ){ |
42 | if( $i != $j ){ |
| 43 | $D->assign($i,$j,0); |
43 | $D->assign($i,$j,0); |
| 44 | }else{ |
44 | }else{ |
| 45 | $D->assign($i,$j,random(-9,9,1)); |
45 | $D->assign($i,$j,random(-9,9,1)); |
| 46 | } |
46 | } |
| 47 | } |
47 | } |
| 48 | } |
48 | } |
| 49 | $norm = abs($D); |
49 | $norm = abs($D); |
| 50 | } |
50 | } |
| 51 | return $D; |
51 | return $D; |
| 52 | } |
52 | } |
| 53 | |
53 | |
| 54 | sub swap_rows{ |
54 | sub swap_rows{ |
| 55 | |
55 | |
| 56 | warn "Usage: \$new_matrix = swap_rows(\$matrix,\$row1,\$row2);" |
56 | warn "Usage: \$new_matrix = swap_rows(\$matrix,\$row1,\$row2);" |
| … | |
… | |
| 110 | that to determine the existence of the change of coordinate matrix going the |
110 | that to determine the existence of the change of coordinate matrix going the |
| 111 | other way. |
111 | other way. |
| 112 | |
112 | |
| 113 | ANS( basis_cmp( vectors_as_array_ref_in_array_ref, options_hash ) ); |
113 | ANS( basis_cmp( vectors_as_array_ref_in_array_ref, options_hash ) ); |
| 114 | |
114 | |
| 115 | 1. a reference to an array of correct vectors |
115 | 1. a reference to an array of correct vectors |
| 116 | 2. a hash with the following keys (all optional): |
116 | 2. a hash with the following keys (all optional): |
| 117 | mode -- 'basis' (default) (only a basis allowed) |
117 | mode -- 'basis' (default) (only a basis allowed) |
| 118 | 'orthogonal' (only an orthogonal basis is allowed) |
118 | 'orthogonal' (only an orthogonal basis is allowed) |
| 119 | 'unit' (only unit vectors in the basis allowed) |
119 | 'unit' (only unit vectors in the basis allowed) |
| 120 | 'orthonormal' (only orthogonal unit vectors in basis allowed) |
120 | 'orthonormal' (only orthogonal unit vectors in basis allowed) |
| 121 | zeroLevelTol -- absolute tolerance to allow when answer is close |
121 | zeroLevelTol -- absolute tolerance to allow when answer is close |
| 122 | to zero |
122 | to zero |
| 123 | |
123 | |
| 124 | debug -- if set to 1, provides verbose listing of |
124 | debug -- if set to 1, provides verbose listing of |
| 125 | hash entries throughout fliters. |
125 | hash entries throughout fliters. |
| 126 | |
126 | |
| 127 | help -- 'none' (default) (is quiet on all errors) |
127 | help -- 'none' (default) (is quiet on all errors) |
| 128 | 'dim' (Tells student if wrong number of vectors are entered) |
128 | 'dim' (Tells student if wrong number of vectors are entered) |
| 129 | 'length' (Tells student if there is a vector of the wrong length) |
129 | 'length' (Tells student if there is a vector of the wrong length) |
| 130 | 'orthogonal' (Tells student if their vectors are not orthogonal) |
130 | 'orthogonal' (Tells student if their vectors are not orthogonal) |
| 131 | (This is only in orthogonal mode) |
131 | (This is only in orthogonal mode) |
| 132 | 'unit' (Tells student if there is a vector not of unit length) |
132 | 'unit' (Tells student if there is a vector not of unit length) |
| 133 | (This is only in unit mode) |
133 | (This is only in unit mode) |
| 134 | 'orthonormal' (Gives errors from orthogonal and orthonormal) |
134 | 'orthonormal' (Gives errors from orthogonal and orthonormal) |
| 135 | (This is only in orthonormal mode) |
135 | (This is only in orthonormal mode) |
| 136 | 'verbose' (Gives all the above answer messages) |
136 | 'verbose' (Gives all the above answer messages) |
| 137 | |
137 | |
| 138 | Returns an answer evaluator. |
138 | Returns an answer evaluator. |
| 139 | |
139 | |
| 140 | EXAMPLES: |
140 | EXAMPLES: |
| 141 | |
141 | |
| 142 | basis_cmp([[1,0,0],[0,1,0],[0,0,1]]) |
142 | basis_cmp([[1,0,0],[0,1,0],[0,0,1]]) |
| 143 | -- correct answer is any basis for R^3. |
143 | -- correct answer is any basis for R^3. |
| 144 | basis_cmp([1,0,2,0],[0,1,0,0], 'mode'=>orthonormal ) |
144 | basis_cmp([1,0,2,0],[0,1,0,0], 'mode'=>orthonormal ) |
| 145 | -- correct answer is any orthonormal basis |
145 | -- correct answer is any orthonormal basis |
| 146 | for this space such as: |
146 | for this space such as: |
| 147 | [1/sqrt(3),0,2/sqrt(3),0],[0,1,0,0] |
147 | [1/sqrt(3),0,2/sqrt(3),0],[0,1,0,0] |
| 148 | |
148 | |
| 149 | =cut |
149 | =cut |
| 150 | |
150 | |
| 151 | |
151 | |
| 152 | sub basis_cmp { |
152 | sub basis_cmp { |
| 153 | my $correctAnswer = shift; |
153 | my $correctAnswer = shift; |
| 154 | my %opt = @_; |
154 | my %opt = @_; |
| 155 | |
155 | |
| 156 | set_default_options( \%opt, |
156 | set_default_options( \%opt, |
| 157 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
157 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
| 158 | 'debug' => 0, |
158 | 'debug' => 0, |
| 159 | 'mode' => 'basis', |
159 | 'mode' => 'basis', |
| 160 | 'help' => 'none', |
160 | 'help' => 'none', |
| 161 | ); |
161 | ); |
| 162 | |
162 | |
| 163 | # produce answer evaluator |
163 | # produce answer evaluator |
| 164 | BASIS_CMP( |
164 | BASIS_CMP( |
| 165 | 'correct_ans' => $correctAnswer, |
165 | 'correct_ans' => $correctAnswer, |
| 166 | 'zeroLevelTol' => $opt{'zeroLevelTol'}, |
166 | 'zeroLevelTol' => $opt{'zeroLevelTol'}, |
| 167 | 'debug' => $opt{'debug'}, |
167 | 'debug' => $opt{'debug'}, |
| 168 | 'mode' => $opt{'mode'}, |
168 | 'mode' => $opt{'mode'}, |
| 169 | 'help' => $opt{'help'}, |
169 | 'help' => $opt{'help'}, |
| 170 | ); |
170 | ); |
| 171 | } |
171 | } |
| 172 | |
172 | |
| 173 | =head BASIS_CMP |
173 | =head BASIS_CMP |
| 174 | |
174 | |
| 175 | Made to keep the same format as num_cmp and fun_cmp. |
175 | Made to keep the same format as num_cmp and fun_cmp. |
| 176 | |
176 | |
| 177 | =cut |
177 | =cut |
| 178 | |
178 | |
| 179 | sub BASIS_CMP { |
179 | sub BASIS_CMP { |
| 180 | my %mat_params = @_; |
180 | my %mat_params = @_; |
| 181 | my $zeroLevelTol = $mat_params{'zeroLevelTol'}; |
181 | my $zeroLevelTol = $mat_params{'zeroLevelTol'}; |
| 182 | |
182 | |
| 183 | # Check that everything is defined: |
183 | # Check that everything is defined: |
| 184 | $mat_params{debug} = 0 unless defined($mat_params{debug}); |
184 | $mat_params{debug} = 0 unless defined($mat_params{debug}); |
| 185 | $zeroLevelTol = $main::functZeroLevelTolDefault unless defined $zeroLevelTol; |
185 | $zeroLevelTol = $main::functZeroLevelTolDefault unless defined $zeroLevelTol; |
| 186 | $mat_params{'zeroLevelTol'} = $zeroLevelTol; |
186 | $mat_params{'zeroLevelTol'} = $zeroLevelTol; |
| 187 | |
187 | |
| 188 | ## This is where the correct answer should be checked someday. |
188 | ## This is where the correct answer should be checked someday. |
| 189 | my $matrix = Matrix->new_from_col_vecs($mat_params{'correct_ans'}); |
189 | my $matrix = Matrix->new_from_col_vecs($mat_params{'correct_ans'}); |
| 190 | |
190 | |
| 191 | #construct the answer evaluator |
191 | #construct the answer evaluator |
| 192 | my $answer_evaluator = new AnswerEvaluator; |
192 | my $answer_evaluator = new AnswerEvaluator; |
| 193 | |
193 | |
| 194 | $answer_evaluator->{debug} = $mat_params{debug}; |
194 | $answer_evaluator->{debug} = $mat_params{debug}; |
| 195 | $answer_evaluator->ans_hash( |
195 | $answer_evaluator->ans_hash( |
| 196 | correct_ans => display_correct_vecs($mat_params{correct_ans}), |
196 | correct_ans => display_correct_vecs($mat_params{correct_ans}), |
| 197 | rm_correct_ans => $matrix, |
197 | rm_correct_ans => $matrix, |
| 198 | zeroLevelTol => $mat_params{zeroLevelTol}, |
198 | zeroLevelTol => $mat_params{zeroLevelTol}, |
| 199 | debug => $mat_params{debug}, |
199 | debug => $mat_params{debug}, |
| 200 | mode => $mat_params{mode}, |
200 | mode => $mat_params{mode}, |
| 201 | help => $mat_params{help}, |
201 | help => $mat_params{help}, |
| 202 | ); |
202 | ); |
| 203 | |
203 | |
| 204 | $answer_evaluator->install_pre_filter( |
204 | $answer_evaluator->install_pre_filter( |
| 205 | sub {my $rh_ans = shift; |
205 | sub {my $rh_ans = shift; |
| 206 | $rh_ans->{_filter_name} = 'remove_white_space'; |
206 | $rh_ans->{_filter_name} = 'remove_white_space'; |
| 207 | $rh_ans->{student_ans} =~ s/\s+//g; # remove all whitespace |
207 | $rh_ans->{student_ans} =~ s/\s+//g; # remove all whitespace |
| 208 | $rh_ans; |
208 | $rh_ans; |
| 209 | } |
209 | } |
| 210 | ); |
210 | ); |
| 211 | $answer_evaluator->install_pre_filter( |
211 | $answer_evaluator->install_pre_filter( |
| 212 | sub{my $rh_ans = shift; |
212 | sub{my $rh_ans = shift; |
| 213 | my @options = @_; |
213 | my @options = @_; |
| 214 | $rh_ans->{_filter_name} = 'mung_student_answer'; |
214 | $rh_ans->{_filter_name} = 'mung_student_answer'; |
| 215 | if( $rh_ans->{ans_label} =~ /ArRaY/ ){ |
215 | if( $rh_ans->{ans_label} =~ /ArRaY/ ){ |
| 216 | $rh_ans = ans_array_filter($rh_ans,@options); |
216 | $rh_ans = ans_array_filter($rh_ans,@options); |
| 217 | my @student_array = @{$rh_ans->{ra_student_ans}}; |
217 | my @student_array = @{$rh_ans->{ra_student_ans}}; |
| 218 | my @array = (); |
218 | my @array = (); |
| 219 | for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) |
219 | for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) |
| 220 | { |
220 | { |
| 221 | push( @array, Matrix->new_from_array_ref($student_array[$i])); |
221 | push( @array, Matrix->new_from_array_ref($student_array[$i])); |
| 222 | } |
222 | } |
| 223 | $rh_ans->{ra_student_ans} = \@array; |
223 | $rh_ans->{ra_student_ans} = \@array; |
| 224 | $rh_ans; |
224 | $rh_ans; |
| 225 | }else{ |
225 | }else{ |
| 226 | $rh_ans->{student_ans} = math_constants($rh_ans->{student_ans}); |
226 | $rh_ans->{student_ans} = math_constants($rh_ans->{student_ans}); |
| 227 | vec_list_string($rh_ans, '_filter_name' => 'vec_list_string', @options); |
227 | vec_list_string($rh_ans, '_filter_name' => 'vec_list_string', @options); |
| 228 | } |
228 | } |
| 229 | } |
229 | } |
| 230 | );#ra_student_ans is now the students answer as an array of vectors |
230 | );#ra_student_ans is now the students answer as an array of vectors |
| 231 | # anonymous subroutine to check dimension and length of the student vectors |
231 | # anonymous subroutine to check dimension and length of the student vectors |
| 232 | # if either is wrong, the answer is wrong. |
232 | # if either is wrong, the answer is wrong. |
| 233 | $answer_evaluator->install_pre_filter( |
233 | $answer_evaluator->install_pre_filter( |
| 234 | sub{ |
234 | sub{ |
| 235 | my $rh_ans = shift; |
235 | my $rh_ans = shift; |
| 236 | $rh_ans->{_filter_name} = 'check_vector_size'; |
236 | $rh_ans->{_filter_name} = 'check_vector_size'; |
| 237 | my $length = $rh_ans->{rm_correct_ans}->[1]; |
237 | my $length = $rh_ans->{rm_correct_ans}->[1]; |
| 238 | my $dim = $rh_ans->{rm_correct_ans}->[2]; |
238 | my $dim = $rh_ans->{rm_correct_ans}->[2]; |
|
|
239 | if( $dim != scalar(@{$rh_ans->{ra_student_ans}})) |
|
|
240 | { |
|
|
241 | |
|
|
242 | $rh_ans->{score} = 0; |
|
|
243 | if( $rh_ans->{help} =~ /dim|verbose/ ) |
|
|
244 | { |
|
|
245 | $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.'); |
|
|
246 | }else{ |
|
|
247 | $rh_ans->throw_error('EVAL'); |
|
|
248 | } |
|
|
249 | } |
|
|
250 | for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) |
|
|
251 | { |
|
|
252 | if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) |
|
|
253 | { |
|
|
254 | $rh_ans->{score} = 0; |
|
|
255 | if( $rh_ans->{help} =~ /length|verbose/ ) |
|
|
256 | { |
|
|
257 | $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.'); |
|
|
258 | }else{ |
|
|
259 | $rh_ans->throw_error('EVAL'); |
|
|
260 | } |
|
|
261 | } |
|
|
262 | } |
|
|
263 | $rh_ans; |
|
|
264 | } |
|
|
265 | ); |
|
|
266 | # Install prefilter for various modes |
|
|
267 | if( $mat_params{mode} ne 'basis' ) |
|
|
268 | { |
|
|
269 | if( $mat_params{mode} =~ /orthogonal|orthonormal/ ) |
|
|
270 | { |
|
|
271 | $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs); |
|
|
272 | } |
|
|
273 | |
|
|
274 | if( $mat_params{mode} =~ /unit|orthonormal/ ) |
|
|
275 | { |
|
|
276 | $answer_evaluator->install_pre_filter(\&are_unit_vecs); |
|
|
277 | |
|
|
278 | } |
|
|
279 | } |
|
|
280 | $answer_evaluator->install_evaluator(\&compare_basis, %mat_params); |
|
|
281 | $answer_evaluator->install_post_filter( |
|
|
282 | sub {my $rh_ans = shift; |
|
|
283 | if ($rh_ans->catch_error('SYNTAX') ) { |
|
|
284 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
|
|
285 | $rh_ans->clear_error('SYNTAX'); |
|
|
286 | } |
|
|
287 | if ($rh_ans->catch_error('EVAL') ) { |
|
|
288 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
|
|
289 | $rh_ans->clear_error('EVAL'); |
|
|
290 | } |
|
|
291 | $rh_ans; |
|
|
292 | } |
|
|
293 | ); |
|
|
294 | $answer_evaluator; |
|
|
295 | } |
|
|
296 | |
|
|
297 | =head4 compare_basis |
|
|
298 | |
|
|
299 | compare_basis( $ans_hash, |
|
|
300 | %options |
|
|
301 | ra_student_ans # a reference to the array of students answer vectors |
|
|
302 | rm_correct_ans, # a reference to the correct answer matrix |
|
|
303 | %options |
|
|
304 | ) |
|
|
305 | |
|
|
306 | |
|
|
307 | =cut |
|
|
308 | |
|
|
309 | |
|
|
310 | |
|
|
311 | sub compare_basis { |
|
|
312 | my ($rh_ans, %options) = @_; |
|
|
313 | $rh_ans->{_filter_name} = "compare_basis"; |
|
|
314 | my @ch_coord; |
|
|
315 | my @vecs = @{$rh_ans->{ra_student_ans}}; |
|
|
316 | |
|
|
317 | # A lot of the following code was taken from Matrix::proj_coeff |
|
|
318 | # calling this method recursively would be a waste of time since |
|
|
319 | # the prof's matrix never changes and solve_LR is an expensive |
|
|
320 | # operation. This way it is only done once. |
|
|
321 | my $matrix = $rh_ans->{rm_correct_ans}; |
|
|
322 | my ($dim,$x_vector, $base_matrix); |
|
|
323 | my $errors = undef; |
|
|
324 | my $lin_space_tr= ~ $matrix; #transpose of the matrix |
|
|
325 | $matrix = $lin_space_tr * $matrix; #(~A * A) |
|
|
326 | my $matrix_lr = $matrix->decompose_LR(); |
|
|
327 | |
|
|
328 | #finds the coefficient vectors for each of the students vectors |
|
|
329 | for( my $i = 0; $i < scalar(@{$rh_ans->{ra_student_ans}}) ; $i++ ) { |
|
|
330 | |
|
|
331 | $vecs[$i] = $lin_space_tr*$vecs[$i]; |
|
|
332 | ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($vecs[$i]); |
|
|
333 | push( @ch_coord, $x_vector ); |
|
|
334 | $errors = "A unique adapted answer could not be determined. |
|
|
335 | Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix |
|
|
336 | is $base_matrix\n" if $dim; # only print if the dim is not zero. |
|
|
337 | } |
|
|
338 | |
|
|
339 | if( defined($errors)) { |
|
|
340 | $rh_ans->throw_error('EVAL', $errors) ; |
|
|
341 | } else { |
|
|
342 | my $ch_coord_mat = Matrix->new_from_col_vecs(\@ch_coord); |
|
|
343 | #creates change of coordinate matrix |
|
|
344 | #existence of this matrix implies that |
|
|
345 | #the all of the students answers are a |
|
|
346 | #linear combo of the prof's |
|
|
347 | $ch_coord_mat = $ch_coord_mat->decompose_LR(); |
|
|
348 | |
|
|
349 | if( abs($ch_coord_mat->det_LR()) > $options{zeroLevelTol} ) { |
|
|
350 | # if the det of the change of coordinate matrix is |
|
|
351 | # non-zero, this implies the existence of an inverse |
|
|
352 | # which implies all of the prof's vectors are a linear |
|
|
353 | # combo of the students vectors, showing containment |
|
|
354 | # both ways. |
|
|
355 | |
|
|
356 | # I think sometimes if the students space has the same dimension as the profs space it |
|
|
357 | # will get projected into the profs space even if it isn't a basis for that space. |
|
|
358 | # this just checks that the prof's matrix times the change of coordinate matrix is actually |
|
|
359 | #the students matrix |
|
|
360 | if( abs(Matrix->new_from_col_vecs(\@{$rh_ans->{ra_student_ans}}) - |
|
|
361 | ($rh_ans->{rm_correct_ans})*(Matrix->new_from_col_vecs(\@ch_coord))) |
|
|
362 | < $options{zeroLevelTol} ) { |
|
|
363 | $rh_ans->{score} = 1; |
|
|
364 | } else { |
|
|
365 | $rh_ans->{score} = 0; |
|
|
366 | } |
|
|
367 | } else { |
|
|
368 | $rh_ans->{score}=0; |
|
|
369 | } |
|
|
370 | } |
|
|
371 | $rh_ans; |
|
|
372 | |
|
|
373 | } |
|
|
374 | |
|
|
375 | |
|
|
376 | =head 2 vec_list_string |
|
|
377 | |
|
|
378 | This is a check_syntax type method (in fact I borrowed some of that method's code) for vector input. |
|
|
379 | The student needs to enter vectors like: [1,0,0],[1,2,3],[0,9/sqrt(10),1/sqrt(10)] |
|
|
380 | Each entry can contain functions and operations and the usual math constants (pi and e). |
|
|
381 | The vectors, however can not be added or multiplied or scalar multiplied by the student. |
|
|
382 | Most errors are handled well. Any error in an entry is caught by the PG_answer_eval like it is in num_cmp or fun_cmp. |
|
|
383 | Right now the method basically ignores every thing outside the vectors. Also, an unmatched open parenthesis is caught, |
|
|
384 | but a unmatched close parenthesis ends the vector, and since everything outside is ignored, no error is sent (other than the |
|
|
385 | later when the length of the vectors is checked. |
|
|
386 | In the end, the method returns an array of Matrix objects. |
|
|
387 | |
|
|
388 | |
|
|
389 | =cut |
|
|
390 | |
|
|
391 | sub vec_list_string{ |
|
|
392 | my $rh_ans = shift; |
|
|
393 | my %options = @_; |
|
|
394 | my $i; |
|
|
395 | my $entry = ""; |
|
|
396 | my $char; |
|
|
397 | my @paren_stack; |
|
|
398 | my $length = length($rh_ans->{student_ans}); |
|
|
399 | my @temp; |
|
|
400 | my $j = 0; |
|
|
401 | my @answers; |
|
|
402 | my $paren; |
|
|
403 | my $display_ans; |
|
|
404 | |
|
|
405 | for( $i = 0; $i < $length ; $i++ ) { |
|
|
406 | $char = substr($rh_ans->{student_ans},$i,1); |
|
|
407 | |
|
|
408 | if( $char =~ /\(|\[|\{/ ){ |
|
|
409 | push( @paren_stack, $char ) |
|
|
410 | } |
|
|
411 | |
|
|
412 | if( !( $char =~ /\(|\[|\{/ && scalar(@paren_stack) == 1 ) ) { |
|
|
413 | if( $char !~ /,|\)|\]|\}/ ){ |
|
|
414 | $entry .= $char; |
|
|
415 | } else { |
|
|
416 | if( $char =~ /,/ || ( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1 ) ) { |
|
|
417 | if( length($entry) == 0 ){ |
|
|
418 | if( $char !~ /,/ ){ |
|
|
419 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
420 | } else { |
|
|
421 | $rh_ans->{preview_text_string} .= ","; |
|
|
422 | $rh_ans->{preview_latex_string} .= ","; |
|
|
423 | $display_ans .= ","; |
|
|
424 | } |
|
|
425 | } else { |
|
|
426 | |
|
|
427 | # This parser code was origianally taken from PGanswermacros::check_syntax |
|
|
428 | # but parts of it needed to be slighty modified for this context |
|
|
429 | my $parser = new AlgParserWithImplicitExpand; |
|
|
430 | my $ret = $parser -> parse($entry); #for use with loops |
|
|
431 | |
|
|
432 | if ( ref($ret) ) { ## parsed successfully |
|
|
433 | $parser -> tostring(); |
|
|
434 | $parser -> normalize(); |
|
|
435 | $entry = $parser -> tostring(); |
|
|
436 | $rh_ans->{preview_text_string} .= $entry.","; |
|
|
437 | $rh_ans->{preview_latex_string} .= $parser -> tolatex().","; |
|
|
438 | |
|
|
439 | } else { ## error in parsing |
|
|
440 | |
|
|
441 | $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror}, |
|
|
442 | $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg}, |
|
|
443 | $rh_ans->{'preview_text_string'} = '', |
|
|
444 | $rh_ans->{'preview_latex_string'} = '', |
|
|
445 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR"); |
|
|
446 | } |
|
|
447 | |
|
|
448 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry); |
|
|
449 | |
|
|
450 | if ($PG_eval_errors) { |
|
|
451 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ; |
|
|
452 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
453 | last; |
|
|
454 | } else { |
|
|
455 | $entry = prfmt($inVal,$options{format}); |
|
|
456 | $display_ans .= $entry.","; |
|
|
457 | push(@temp , $entry); |
|
|
458 | } |
|
|
459 | |
|
|
460 | if( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1) { |
|
|
461 | pop @paren_stack; |
|
|
462 | chop($rh_ans->{preview_text_string}); |
|
|
463 | chop($rh_ans->{preview_latex_string}); |
|
|
464 | chop($display_ans); |
|
|
465 | $rh_ans->{preview_text_string} .= "]"; |
|
|
466 | $rh_ans->{preview_latex_string} .= "]"; |
|
|
467 | $display_ans .= "]"; |
|
|
468 | if( scalar(@temp) > 0 ) { |
|
|
469 | push( @answers,Matrix->new_from_col_vecs([\@temp])); |
|
|
470 | while(scalar(@temp) > 0 ){ |
|
|
471 | pop @temp; |
|
|
472 | } |
|
|
473 | } else { |
|
|
474 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.'); |
|
|
475 | } |
|
|
476 | } |
|
|
477 | } |
|
|
478 | $entry = ""; |
|
|
479 | } else { |
|
|
480 | $paren = pop @paren_stack; |
|
|
481 | if( scalar(@paren_stack) > 0 ){ |
|
|
482 | #this uses ASCII to check if the parens match up |
|
|
483 | # in ASCII ord ( = 40 , ord ) = 41 , ord [ = 91 , |
|
|
484 | # ord ] = 93 , ord { = 123 , ord } = 125 |
|
|
485 | if( (ord($char) - ord($paren) <= 2) ){ |
|
|
486 | $entry = $entry . $char; |
|
|
487 | }else{ |
|
|
488 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
489 | } |
|
|
490 | } |
|
|
491 | } |
|
|
492 | } |
|
|
493 | } else { |
|
|
494 | $rh_ans->{preview_text_string} .= "["; |
|
|
495 | $rh_ans->{preview_latex_string} .= "["; |
|
|
496 | $display_ans .= "["; |
|
|
497 | } |
|
|
498 | } |
|
|
499 | $rh_ans->{ra_student_ans} = \@answers; |
|
|
500 | $rh_ans->{student_ans} = $display_ans unless $rh_ans->{error_flag}; |
|
|
501 | $rh_ans; |
|
|
502 | } |
|
|
503 | |
|
|
504 | =head5 |
|
|
505 | |
|
|
506 | This filter was created to get, format, and evaluate each entry of the ans_array and ans_array_extension |
|
|
507 | answer entry methods. Running this filter is necessary to get all the entries out of the answer |
|
|
508 | hash. Each entry is evaluated and the resulting number is put in the display for student answer |
|
|
509 | as a string. For evaluation purposes an array of arrays of arrays is created called ra_student_ans |
|
|
510 | and placed in the hash. The entries are [array_number][row_number][column_number]. The latex strings |
|
|
511 | for each entry are taken from the parser and put, as a matrix, into the previewer. The preview text |
|
|
512 | string is also created, but this display method becomes confusing when large matrices are used. |
|
|
513 | |
|
|
514 | =cut |
|
|
515 | |
|
|
516 | |
|
|
517 | sub ans_array_filter{ |
|
|
518 | my $rh_ans = shift; |
|
|
519 | my %options = @_; |
|
|
520 | # assign_option_aliases( \%opt, |
|
|
521 | # ); |
|
|
522 | set_default_options(\%options, |
|
|
523 | _filter_name => 'ans_array_filter', |
|
|
524 | ); |
|
|
525 | # $rh_ans->{ans_label} =~ /ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accomodate HTML 4.01 standards for name attribute |
|
|
526 | $rh_ans->{ans_label} =~ /ArRaY(\d+)\_\_\d+:\d+:\d+\_\_/; |
|
|
527 | my $ans_num = $1; |
|
|
528 | my @keys = grep /ArRaY$ans_num/, keys(%{$main::inputs_ref}); |
|
|
529 | my $key; |
|
|
530 | my @array = (); |
|
|
531 | my ($i,$j,$k) = (0,0,0); |
|
|
532 | |
|
|
533 | #the keys aren't in order, so their info has to be put into the array before doing anything with it |
|
|
534 | foreach $key (@keys){ |
|
|
535 | # $key =~ /ArRaY\d+\[(\d+),(\d+),(\d+)\]/; |
|
|
536 | # ($i,$j,$k) = ($1,$2,$3); |
|
|
537 | # $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'['.$i.','.$j.','.$k.']'}; |
|
|
538 | $key =~ /ArRaY\d+\_\_(\d+):(\d+):(\d+)\_\_/; |
|
|
539 | ($i,$j,$k) = ($1,$2,$3); |
|
|
540 | $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'__'.$i.':'.$j.':'.$k.'__'}; |
|
|
541 | |
|
|
542 | } |
|
|
543 | #$rh_ans->{debug_student_answer }= \@array; |
|
|
544 | my $display_ans = ""; |
|
|
545 | |
|
|
546 | for( $i=0; $i < scalar(@array) ; $i ++ ) { |
|
|
547 | $display_ans .= " ["; |
|
|
548 | $rh_ans->{preview_text_string} .= ' ['; |
|
|
549 | $rh_ans->{preview_latex_string} .= '\begin{pmatrix} '; |
|
|
550 | for( $j = 0; $j < scalar( @{$array[$i]} ) ; $j++ ) { |
|
|
551 | $display_ans .= " ["; |
|
|
552 | $rh_ans->{preview_text_string} .= ' ['; |
|
|
553 | for( $k = 0; $k < scalar( @{$array[$i][$j]} ) ; $k ++ ){ |
|
|
554 | my $entry = $array[$i][$j][$k]; |
|
|
555 | $entry = math_constants($entry); |
|
|
556 | # This parser code was origianally taken from PGanswermacros::check_syntax |
|
|
557 | # but parts of it needed to be slighty modified for this context |
|
|
558 | my $parser = new AlgParserWithImplicitExpand; |
|
|
559 | my $ret = $parser -> parse($entry); #for use with loops |
|
|
560 | |
|
|
561 | if ( ref($ret) ) { ## parsed successfully |
|
|
562 | $parser -> tostring(); |
|
|
563 | $parser -> normalize(); |
|
|
564 | $entry = $parser -> tostring(); |
|
|
565 | $rh_ans->{preview_text_string} .= $entry.","; |
|
|
566 | $rh_ans->{preview_latex_string} .= $parser -> tolatex() . '& '; |
|
|
567 | |
|
|
568 | } else { ## error in parsing |
|
|
569 | $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror}, |
|
|
570 | $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg}, |
|
|
571 | $rh_ans->{'preview_text_string'} = '', |
|
|
572 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR"); |
|
|
573 | } |
|
|
574 | |
|
|
575 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry); |
|
|
576 | if ($PG_eval_errors) { |
|
|
577 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ; |
|
|
578 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
579 | last; |
|
|
580 | } else { |
|
|
581 | $entry = prfmt($inVal,$options{format}); |
|
|
582 | $display_ans .= $entry.","; |
|
|
583 | $array[$i][$j][$k] = $entry; |
|
|
584 | } |
|
|
585 | } |
|
|
586 | chop($rh_ans->{preview_text_string}); |
|
|
587 | chop($display_ans); |
|
|
588 | $rh_ans->{preview_text_string} .= '] ,'; |
|
|
589 | $rh_ans->{preview_latex_string} .= '\\\\'; |
|
|
590 | $display_ans .= '] ,'; |
|
|
591 | |
|
|
592 | } |
|
|
593 | chop($rh_ans->{preview_text_string}); |
|
|
594 | chop($display_ans); |
|
|
595 | $rh_ans->{preview_text_string} .= '] ,'; |
|
|
596 | $rh_ans->{preview_latex_string} .= '\end{pmatrix}'.' , '; |
|
|
597 | $display_ans .= '] ,'; |
|
|
598 | } |
|
|
599 | chop($rh_ans->{preview_text_string}); |
|
|
600 | chop($rh_ans->{preview_latex_string}); |
|
|
601 | chop($rh_ans->{preview_latex_string}); |
|
|
602 | chop($rh_ans->{preview_latex_string}); |
|
|
603 | chop($display_ans); |
|
|
604 | |
|
|
605 | my @temp = (); |
|
|
606 | for( $i = 0 ; $i < scalar( @array ); $i++ ){ |
|
|
607 | push @temp , display_matrix($array[$i], 'left'=>'.', 'right'=>'.'); |
|
|
608 | push @temp , "," unless $i == scalar(@array) - 1; |
|
|
609 | } |
|
|
610 | $rh_ans->{student_ans} = mbox(\@temp); |
|
|
611 | $rh_ans->{ra_student_ans} = \@array; |
|
|
612 | |
|
|
613 | $rh_ans; |
|
|
614 | |
|
|
615 | } |
|
|
616 | |
|
|
617 | |
|
|
618 | sub are_orthogonal_vecs{ |
|
|
619 | my ($vec_ref , %opts) = @_; |
|
|
620 | $vec_ref->{_filter_name} = 'are_orthogonal_vecs'; |
|
|
621 | my @vecs = (); |
|
|
622 | if( ref($vec_ref) eq 'AnswerHash' ) |
|
|
623 | { |
|
|
624 | @vecs = @{$vec_ref->{ra_student_ans}}; |
|
|
625 | }else{ |
|
|
626 | @vecs = @{$vec_ref}; |
|
|
627 | } |
|
|
628 | |
|
|
629 | my $num = scalar(@vecs); |
|
|
630 | my $length = $vecs[0]->[1]; |
|
|
631 | |
|
|
632 | for( my $i=0; $i < $num ; $i ++ ) { |
|
|
633 | for( my $j = $i+1; $j < $num ; $j++ ) { |
|
|
634 | if( $vecs[$i]->scalar_product($vecs[$j]) > $main::functZeroLevelTolDefault ) { |
|
|
635 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
636 | $vec_ref->{score} = 0; |
|
|
637 | if( $vec_ref->{help} =~ /orthogonal|orthonormal|verbose/ ) |
|
|
638 | { |
|
|
639 | $vec_ref->throw_error('EVAL','You have entered vectors which are not orthogonal. '); |
|
|
640 | }else{ |
|
|
641 | $vec_ref->throw_error('EVAL'); |
|
|
642 | } |
|
|
643 | return $vec_ref; |
|
|
644 | } else { |
|
|
645 | return 0; |
|
|
646 | } |
|
|
647 | } |
|
|
648 | } |
|
|
649 | } |
|
|
650 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
651 | $vec_ref->{score} = 1; |
|
|
652 | $vec_ref; |
|
|
653 | } else { |
|
|
654 | 1; |
|
|
655 | } |
|
|
656 | } |
|
|
657 | |
|
|
658 | sub is_diagonal{ |
|
|
659 | my $matrix = shift; |
|
|
660 | my %options = @_; |
|
|
661 | my $process_ans_hash = ( ref( $matrix ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
662 | my ($rh_ans); |
|
|
663 | if ($process_ans_hash) { |
|
|
664 | $rh_ans = $matrix; |
|
|
665 | $matrix = $rh_ans->{ra_student_ans}; |
|
|
666 | } |
|
|
667 | |
|
|
668 | return 0 unless defined($matrix); |
|
|
669 | |
|
|
670 | if( ref($matrix) eq 'ARRAY' ) { |
|
|
671 | my @matrix = @{$matrix}; |
|
|
672 | @matrix = @{$matrix[0]} if ref($matrix[0][0]) eq 'ARRAY'; |
|
|
673 | if( ref($matrix[0]) ne 'ARRAY' or scalar( @matrix ) != scalar( @{$matrix[0]} ) ){ |
|
|
674 | warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem."; |
|
|
675 | } |
|
|
676 | |
|
|
677 | for( my $i = 0; $i < scalar( @matrix ) ; $i++ ) { |
|
|
678 | for( my $j = 0; $j < scalar( @{$matrix[0]} ); $j++ ){ |
|
|
679 | if( $matrix[$i][$j] != 0 and $i != $j ) |
|
|
680 | { |
|
|
681 | if ($process_ans_hash){ |
|
|
682 | $rh_ans->throw_error('EVAL'); |
|
|
683 | return $rh_ans; |
|
|
684 | } else { |
|
|
685 | return 0; |
|
|
686 | } |
|
|
687 | } |
|
|
688 | } |
|
|
689 | } |
|
|
690 | if ($process_ans_hash){ |
|
|
691 | return $rh_ans; |
|
|
692 | } else { |
|
|
693 | return 1; |
|
|
694 | } |
|
|
695 | } elsif ( ref($matrix) eq 'Matrix' ) { |
|
|
696 | if( $matrix->[1] != $matrix->[2] ) { |
|
|
697 | warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem."; |
|
|
698 | if ($process_ans_hash){ |
|
|
699 | $rh_ans->throw_error('EVAL'); |
|
|
700 | return $rh_ans; |
|
|
701 | } else { |
|
|
702 | return 0; |
|
|
703 | } |
|
|
704 | } |
|
|
705 | for( my $i = 0; $i < $matrix->[1] ; $i++ ) { |
|
|
706 | for( my $j = 0; $j < $matrix->[2] ; $j++ ) { |
|
|
707 | if( $matrix->[0][$i][$j] != 0 and $i != $j ){ |
|
|
708 | if ($process_ans_hash){ |
|
|
709 | $rh_ans->throw_error('EVAL'); |
|
|
710 | return $rh_ans; |
|
|
711 | } else { |
|
|
712 | return 0; |
|
|
713 | } |
|
|
714 | } |
|
|
715 | } |
|
|
716 | } |
|
|
717 | if ($process_ans_hash) { |
|
|
718 | return $rh_ans; |
|
|
719 | } else { |
|
|
720 | return 1; |
|
|
721 | } |
|
|
722 | } else { |
|
|
723 | warn "There is a problem with the problem, please alert your professor."; |
|
|
724 | if ($process_ans_hash){ |
|
|
725 | $rh_ans->throw_error('EVAL'); |
|
|
726 | return $rh_ans; |
|
|
727 | } else { |
|
|
728 | return 0; |
|
|
729 | } |
|
|
730 | } |
|
|
731 | |
|
|
732 | } |
|
|
733 | |
|
|
734 | |
|
|
735 | sub are_unit_vecs{ |
|
|
736 | my ( $vec_ref,%opts ) = @_; |
|
|
737 | $vec_ref->{_filter_name} = 'are_unit_vecs'; |
|
|
738 | my @vecs = (); |
|
|
739 | if( ref($vec_ref) eq 'AnswerHash' ) |
|
|
740 | { |
|
|
741 | @vecs = @{$vec_ref->{ra_student_ans}}; |
|
|
742 | }else{ |
|
|
743 | @vecs = @{$vec_ref}; |
|
|
744 | } |
|
|
745 | |
|
|
746 | my $i = 0; |
|
|
747 | my $num = scalar(@vecs); |
|
|
748 | my $length = $vecs[0]->[1]; |
|
|
749 | |
|
|
750 | for( ; $i < $num ; $i ++ ) { |
|
|
751 | if( abs(sqrt($vecs[$i]->scalar_product($vecs[$i]))- 1) > $main::functZeroLevelTolDefault ) |
|
|
752 | { |
|
|
753 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
754 | $vec_ref->{score} = 0; |
|
|
755 | if( $vec_ref->{help} =~ /unit|orthonormal|verbose/ ) |
|
|
756 | { |
|
|
757 | $vec_ref->throw_error('EVAL','You have entered vector(s) which are not of unit length.'); |
|
|
758 | }else{ |
|
|
759 | $vec_ref->throw_error('EVAL'); |
|
|
760 | } |
|
|
761 | return $vec_ref; |
|
|
762 | }else{ |
|
|
763 | return 0; |
|
|
764 | } |
|
|
765 | |
|
|
766 | } |
|
|
767 | } |
|
|
768 | |
|
|
769 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
770 | $vec_ref->{score} = 1; |
|
|
771 | $vec_ref; |
|
|
772 | }else{ |
|
|
773 | 1; |
|
|
774 | } |
|
|
775 | } |
|
|
776 | |
|
|
777 | sub display_correct_vecs{ |
|
|
778 | my ( $ra_vecs,%opts ) = @_; |
|
|
779 | my @ra_vecs = @{$ra_vecs}; |
|
|
780 | my @temp = (); |
|
|
781 | |
|
|
782 | for( my $i = 0 ; $i < scalar(@ra_vecs) ; $i++ ) { |
|
|
783 | push @temp, display_matrix(Matrix->new_from_col_vecs([$ra_vecs[$i]]),'left'=>'.','right'=>'.'); |
|
|
784 | push @temp, ","; |
|
|
785 | } |
|
|
786 | |
|
|
787 | pop @temp; |
|
|
788 | |
|
|
789 | mbox(\@temp); |
|
|
790 | |
|
|
791 | } |
|
|
792 | |
|
|
793 | sub vec_solution_cmp{ |
|
|
794 | my $correctAnswer = shift; |
|
|
795 | my %opt = @_; |
|
|
796 | |
|
|
797 | set_default_options( \%opt, |
|
|
798 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
|
|
799 | 'debug' => 0, |
|
|
800 | 'mode' => 'basis', |
|
|
801 | 'help' => 'none', |
|
|
802 | ); |
|
|
803 | |
|
|
804 | |
|
|
805 | ## This is where the correct answer should be checked someday. |
|
|
806 | my $matrix = Matrix->new_from_col_vecs($correctAnswer); |
|
|
807 | |
|
|
808 | |
|
|
809 | #construct the answer evaluator |
|
|
810 | my $answer_evaluator = new AnswerEvaluator; |
|
|
811 | |
|
|
812 | $answer_evaluator->{debug} = $opt{debug}; |
|
|
813 | $answer_evaluator->ans_hash( |
|
|
814 | correct_ans => display_correct_vecs($correctAnswer), |
|
|
815 | old_correct_ans => $correctAnswer, |
|
|
816 | rm_correct_ans => $matrix, |
|
|
817 | zeroLevelTol => $opt{zeroLevelTol}, |
|
|
818 | debug => $opt{debug}, |
|
|
819 | mode => $opt{mode}, |
|
|
820 | help => $opt{help}, |
|
|
821 | ); |
|
|
822 | |
|
|
823 | $answer_evaluator->install_pre_filter(\&ans_array_filter); |
|
|
824 | $answer_evaluator->install_pre_filter( |
|
|
825 | sub{ |
|
|
826 | my ($rh_ans,@options) = @_; |
|
|
827 | $rh_ans->{_filter_name} = "create student answer as an array of vectors"; |
|
|
828 | my @student_array = @{$rh_ans->{ra_student_ans}}; |
|
|
829 | my @array = (); |
|
|
830 | for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) { |
|
|
831 | push( @array, Matrix->new_from_array_ref($student_array[$i])); |
|
|
832 | } |
|
|
833 | $rh_ans->{ra_student_ans} = \@array; |
|
|
834 | $rh_ans; |
|
|
835 | } |
|
|
836 | ); |
|
|
837 | #ra_student_ans is now the students answer as an array of vectors |
|
|
838 | # anonymous subroutine to check dimension and length of the student vectors |
|
|
839 | # if either is wrong, the answer is wrong. |
|
|
840 | $answer_evaluator->install_pre_filter( |
|
|
841 | sub{ |
|
|
842 | my $rh_ans = shift; |
|
|
843 | $rh_ans->{_filter_name} = "check_dimension_and_length"; |
|
|
844 | my $length = $rh_ans->{rm_correct_ans}->[1]; |
|
|
845 | my $dim = $rh_ans->{rm_correct_ans}->[2]; |
| 239 | if( $dim != scalar(@{$rh_ans->{ra_student_ans}})) |
846 | if( $dim != scalar(@{$rh_ans->{ra_student_ans}})) |
| 240 | { |
847 | { |
| 241 | |
848 | |
| 242 | $rh_ans->{score} = 0; |
849 | $rh_ans->{score} = 0; |
| 243 | if( $rh_ans->{help} =~ /dim|verbose/ ) |
850 | if( $rh_ans->{help} =~ /dim|verbose/ ) |
| … | |
… | |
| 245 | $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.'); |
852 | $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.'); |
| 246 | }else{ |
853 | }else{ |
| 247 | $rh_ans->throw_error('EVAL'); |
854 | $rh_ans->throw_error('EVAL'); |
| 248 | } |
855 | } |
| 249 | } |
856 | } |
| 250 | for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) |
857 | for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) { |
| 251 | { |
|
|
| 252 | if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) |
858 | if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) { |
| 253 | { |
|
|
| 254 | $rh_ans->{score} = 0; |
859 | $rh_ans->{score} = 0; |
| 255 | if( $rh_ans->{help} =~ /length|verbose/ ) |
860 | if( $rh_ans->{help} =~ /length|verbose/ ) { |
| 256 | { |
|
|
| 257 | $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.'); |
861 | $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.'); |
| 258 | }else{ |
862 | }else{ |
| 259 | $rh_ans->throw_error('EVAL'); |
863 | $rh_ans->throw_error('EVAL'); |
| 260 | } |
864 | } |
| 261 | } |
865 | } |
| 262 | } |
866 | } |
| 263 | $rh_ans; |
867 | $rh_ans; |
| 264 | } |
868 | } |
| 265 | ); |
869 | ); |
| 266 | # Install prefilter for various modes |
870 | # Install prefilter for various modes |
| 267 | if( $mat_params{mode} ne 'basis' ) |
871 | if( $opt{mode} ne 'basis' ) { |
| 268 | { |
|
|
| 269 | if( $mat_params{mode} =~ /orthogonal|orthonormal/ ) |
872 | if( $opt{mode} =~ /orthogonal|orthonormal/ ) { |
| 270 | { |
|
|
| 271 | $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs); |
873 | $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs); |
| 272 | } |
874 | } |
| 273 | |
875 | |
| 274 | if( $mat_params{mode} =~ /unit|orthonormal/ ) |
876 | if( $opt{mode} =~ /unit|orthonormal/ ) { |
| 275 | { |
|
|
| 276 | $answer_evaluator->install_pre_filter(\&are_unit_vecs); |
877 | $answer_evaluator->install_pre_filter(\&are_unit_vecs); |
| 277 | |
878 | |
| 278 | } |
879 | } |
| 279 | } |
|
|
| 280 | $answer_evaluator->install_evaluator(\&compare_basis, %mat_params); |
|
|
| 281 | $answer_evaluator->install_post_filter( |
|
|
| 282 | sub {my $rh_ans = shift; |
|
|
| 283 | if ($rh_ans->catch_error('SYNTAX') ) { |
|
|
| 284 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
|
|
| 285 | $rh_ans->clear_error('SYNTAX'); |
|
|
| 286 | } |
|
|
| 287 | if ($rh_ans->catch_error('EVAL') ) { |
|
|
| 288 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
|
|
| 289 | $rh_ans->clear_error('EVAL'); |
|
|
| 290 | } |
|
|
| 291 | $rh_ans; |
|
|
| 292 | } |
|
|
| 293 | ); |
|
|
| 294 | $answer_evaluator; |
|
|
| 295 | } |
|
|
| 296 | |
|
|
| 297 | =head4 compare_basis |
|
|
| 298 | |
|
|
| 299 | compare_basis( $ans_hash, %options); |
|
|
| 300 | |
|
|
| 301 | {ra_student_ans}, # a reference to the array of students answer vectors |
|
|
| 302 | {rm_correct_ans}, # a reference to the correct answer matrix |
|
|
| 303 | %options |
|
|
| 304 | ) |
|
|
| 305 | |
|
|
| 306 | =cut |
|
|
| 307 | |
|
|
| 308 | sub compare_basis { |
|
|
| 309 | my ($rh_ans, %options) = @_; |
|
|
| 310 | my @ch_coord; |
|
|
| 311 | my @vecs = @{$rh_ans->{ra_student_ans}}; |
|
|
| 312 | |
|
|
| 313 | # A lot of the follosing code was taken from Matrix::proj_coeff |
|
|
| 314 | # calling this method recursively would be a waste of time since |
|
|
| 315 | # the prof's matrix never changes and solve_LR is an expensive |
|
|
| 316 | # operation. This way it is only done once. |
|
|
| 317 | my $matrix = $rh_ans->{rm_correct_ans}; |
|
|
| 318 | my ($dim,$x_vector, $base_matrix); |
|
|
| 319 | my $errors = undef; |
|
|
| 320 | my $lin_space_tr= ~ $matrix; |
|
|
| 321 | $matrix = $lin_space_tr * $matrix; |
|
|
| 322 | my $matrix_lr = $matrix->decompose_LR(); |
|
|
| 323 | |
|
|
| 324 | #finds the coefficient vectors for each of the students vectors |
|
|
| 325 | for( my $i = 0; $i < scalar(@{$rh_ans->{ra_student_ans}}) ; $i++ ) |
|
|
| 326 | { |
|
|
| 327 | |
|
|
| 328 | $vecs[$i] = $lin_space_tr*$vecs[$i]; |
|
|
| 329 | ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($vecs[$i]); |
|
|
| 330 | push( @ch_coord, $x_vector ); |
|
|
| 331 | $errors = "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" if $dim; # only print if the dim is not zero. |
|
|
| 332 | } |
|
|
| 333 | |
|
|
| 334 | if( defined($errors)) |
|
|
| 335 | { |
|
|
| 336 | $rh_ans->throw_error('EVAL', $errors) ; |
|
|
| 337 | }else{ |
|
|
| 338 | my $ch_coord_mat = Matrix->new_from_col_vecs(\@ch_coord);#creates change of coordinate matrix |
|
|
| 339 | #existence of this matrix implies that |
|
|
| 340 | #the all of the students answers are a |
|
|
| 341 | #linear combo of the prof's |
|
|
| 342 | $ch_coord_mat = $ch_coord_mat->decompose_LR(); |
|
|
| 343 | |
|
|
| 344 | if( abs($ch_coord_mat->det_LR()) > $options{zeroLevelTol} )# if the det of the change of coordinate matrix is |
|
|
| 345 | # non-zero, this implies the existence of an inverse |
|
|
| 346 | # which implies all of the prof's vectors are a linear |
|
|
| 347 | # combo of the students vectors, showing containment |
|
|
| 348 | # both ways. |
|
|
| 349 | { |
|
|
| 350 | # I think sometimes if the students space has the same dimension as the profs space it |
|
|
| 351 | # will get projected into the profs space even if it isn't a basis for that space. |
|
|
| 352 | # this just checks that the prof's matrix times the change of coordinate matrix is actually |
|
|
| 353 | #the students matrix |
|
|
| 354 | if( abs(Matrix->new_from_col_vecs(\@{$rh_ans->{ra_student_ans}}) - ($rh_ans->{rm_correct_ans})*(Matrix->new_from_col_vecs(\@ch_coord))) < $options{zeroLevelTol} ) |
|
|
| 355 | { |
|
|
| 356 | $rh_ans->{score} = 1; |
|
|
| 357 | }else{ |
|
|
| 358 | $rh_ans->{score} = 0; |
|
|
| 359 | } |
|
|
| 360 | } |
|
|
| 361 | else{ |
|
|
| 362 | $rh_ans->{score}=0; |
|
|
| 363 | } |
|
|
| 364 | } |
|
|
| 365 | $rh_ans; |
|
|
| 366 | |
|
|
| 367 | } |
|
|
| 368 | |
|
|
| 369 | |
|
|
| 370 | =head 2 vec_list_string |
|
|
| 371 | |
|
|
| 372 | This is a check_syntax type method (in fact I borrowed some of that method's code) for vector input. |
|
|
| 373 | The student needs to enter vectors like: [1,0,0],[1,2,3],[0,9/sqrt(10),1/sqrt(10)] |
|
|
| 374 | Each entry can contain functions and operations and the usual math constants (pi and e). |
|
|
| 375 | The vectors, however can not be added or multiplied or scalar multiplied by the student. |
|
|
| 376 | Most errors are handled well. Any error in an entry is caught by the PG_answer_eval like it is in num_cmp or fun_cmp. |
|
|
| 377 | Right now the method basically ignores every thing outside the vectors. Also, an unmatched open parenthesis is caught, |
|
|
| 378 | but a unmatched close parenthesis ends the vector, and since everything outside is ignored, no error is sent (other than the |
|
|
| 379 | later when the length of the vectors is checked. |
|
|
| 380 | In the end, the method returns an array of Matrix objects. |
|
|
| 381 | |
|
|
| 382 | |
|
|
| 383 | =cut |
|
|
| 384 | |
|
|
| 385 | sub vec_list_string{ |
|
|
| 386 | my $rh_ans = shift; |
|
|
| 387 | my %options = @_; |
|
|
| 388 | my $i; |
|
|
| 389 | my $entry = ""; |
|
|
| 390 | my $char; |
|
|
| 391 | my @paren_stack; |
|
|
| 392 | my $length = length($rh_ans->{student_ans}); |
|
|
| 393 | my @temp; |
|
|
| 394 | my $j = 0; |
|
|
| 395 | my @answers; |
|
|
| 396 | my $paren; |
|
|
| 397 | my $display_ans; |
|
|
| 398 | |
|
|
| 399 | for( $i = 0; $i < $length ; $i++ ) |
|
|
| 400 | { |
|
|
| 401 | $char = substr($rh_ans->{student_ans},$i,1); |
|
|
| 402 | |
|
|
| 403 | if( $char =~ /\(|\[|\{/ ){ |
|
|
| 404 | push( @paren_stack, $char ) |
|
|
| 405 | } |
|
|
| 406 | |
|
|
| 407 | if( !( $char =~ /\(|\[|\{/ && scalar(@paren_stack) == 1 ) ) |
|
|
| 408 | { |
|
|
| 409 | if( $char !~ /,|\)|\]|\}/ ){ |
|
|
| 410 | $entry .= $char; |
|
|
| 411 | }else{ |
|
|
| 412 | if( $char =~ /,/ || ( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1 ) ) |
|
|
| 413 | { |
|
|
| 414 | if( length($entry) == 0 ){ |
|
|
| 415 | if( $char !~ /,/ ){ |
|
|
| 416 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
| 417 | }else{ |
|
|
| 418 | $rh_ans->{preview_text_string} .= ","; |
|
|
| 419 | $rh_ans->{preview_latex_string} .= ","; |
|
|
| 420 | $display_ans .= ","; |
|
|
| 421 | } |
|
|
| 422 | }else{ |
|
|
| 423 | |
|
|
| 424 | # This parser code was origianally taken from PGanswermacros::check_syntax |
|
|
| 425 | # but parts of it needed to be slighty modified for this context |
|
|
| 426 | my $parser = new AlgParserWithImplicitExpand; |
|
|
| 427 | my $ret = $parser -> parse($entry); #for use with loops |
|
|
| 428 | |
|
|
| 429 | if ( ref($ret) ) { ## parsed successfully |
|
|
| 430 | $parser -> tostring(); |
|
|
| 431 | $parser -> normalize(); |
|
|
| 432 | $entry = $parser -> tostring(); |
|
|
| 433 | $rh_ans->{preview_text_string} .= $entry.","; |
|
|
| 434 | $rh_ans->{preview_latex_string} .= $parser -> tolatex().","; |
|
|
| 435 | |
|
|
| 436 | } else { ## error in parsing |
|
|
| 437 | |
|
|
| 438 | $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror}, |
|
|
| 439 | $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg}, |
|
|
| 440 | $rh_ans->{'preview_text_string'} = '', |
|
|
| 441 | $rh_ans->{'preview_latex_string'} = '', |
|
|
| 442 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR"); |
|
|
| 443 | } |
|
|
| 444 | |
|
|
| 445 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry); |
|
|
| 446 | |
|
|
| 447 | if ($PG_eval_errors) { |
|
|
| 448 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ; |
|
|
| 449 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
| 450 | last; |
|
|
| 451 | } else { |
|
|
| 452 | $entry = prfmt($inVal,$options{format}); |
|
|
| 453 | $display_ans .= $entry.","; |
|
|
| 454 | push(@temp , $entry); |
|
|
| 455 | } |
|
|
| 456 | |
|
|
| 457 | if( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1) |
|
|
| 458 | { |
|
|
| 459 | pop @paren_stack; |
|
|
| 460 | chop($rh_ans->{preview_text_string}); |
|
|
| 461 | chop($rh_ans->{preview_latex_string}); |
|
|
| 462 | chop($display_ans); |
|
|
| 463 | $rh_ans->{preview_text_string} .= "]"; |
|
|
| 464 | $rh_ans->{preview_latex_string} .= "]"; |
|
|
| 465 | $display_ans .= "]"; |
|
|
| 466 | if( scalar(@temp) > 0 ) |
|
|
| 467 | { |
|
|
| 468 | push( @answers,Matrix->new_from_col_vecs([\@temp])); |
|
|
| 469 | while(scalar(@temp) > 0 ){ |
|
|
| 470 | pop @temp; |
|
|
| 471 | } |
|
|
| 472 | }else{ |
|
|
| 473 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.'); |
|
|
| 474 | } |
|
|
| 475 | } |
|
|
| 476 | } |
|
|
| 477 | $entry = ""; |
|
|
| 478 | }else{ |
|
|
| 479 | $paren = pop @paren_stack; |
|
|
| 480 | if( scalar(@paren_stack) > 0 ){ |
|
|
| 481 | #this uses ASCII to check if the parens match up |
|
|
| 482 | # in ASCII ord ( = 40 , ord ) = 41 , ord [ = 91 , |
|
|
| 483 | # ord ] = 93 , ord { = 123 , ord } = 125 |
|
|
| 484 | if( (ord($char) - ord($paren) <= 2) ){ |
|
|
| 485 | $entry = $entry . $char; |
|
|
| 486 | }else{ |
|
|
| 487 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
| 488 | } |
|
|
| 489 | } |
|
|
| 490 | } |
|
|
| 491 | } |
|
|
| 492 | }else{ |
|
|
| 493 | $rh_ans->{preview_text_string} .= "["; |
|
|
| 494 | $rh_ans->{preview_latex_string} .= "["; |
|
|
| 495 | $display_ans .= "["; |
|
|
| 496 | } |
|
|
| 497 | } |
|
|
| 498 | $rh_ans->{ra_student_ans} = \@answers; |
|
|
| 499 | $rh_ans->{student_ans} = $display_ans unless $rh_ans->{error_flag}; |
|
|
| 500 | $rh_ans; |
|
|
| 501 | } |
|
|
| 502 | |
|
|
| 503 | =head5 |
|
|
| 504 | This filter was created to get, format, and evaluate each entry of the ans_array and ans_array_extension |
|
|
| 505 | answer entry methods. Running this filter is necessary to get all the entries out of the answer |
|
|
| 506 | hash. Each entry is evaluated and the resulting number is put in the display for student answer |
|
|
| 507 | as a string. For evaluation purposes an array of arrays of arrays is created called ra_student_ans |
|
|
| 508 | and placed in the hash. The entries are [array_number][row_number][column_number]. The latex strings |
|
|
| 509 | for each entry are taken from the parser and put, as a matrix, into the previewer. The preview text |
|
|
| 510 | string is also created, but this display method becomes confusing when large matrices are used. |
|
|
| 511 | =cut |
|
|
| 512 | |
|
|
| 513 | |
|
|
| 514 | sub ans_array_filter{ |
|
|
| 515 | my $rh_ans = shift; |
|
|
| 516 | my %options = @_; |
|
|
| 517 | # assign_option_aliases( \%opt, |
|
|
| 518 | # ); |
|
|
| 519 | set_default_options(\%options, |
|
|
| 520 | '_filter_name' => 'ans_array_filter', |
|
|
| 521 | ); |
|
|
| 522 | # $rh_ans->{ans_label} =~ /ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accomodate HTML 4.01 standards for name attribute |
|
|
| 523 | $rh_ans->{ans_label} =~ /ArRaY(\d+)\_\_\d+:\d+:\d+\_\_/; |
|
|
| 524 | my $ans_num = $1; |
|
|
| 525 | my @keys = grep /ArRaY$ans_num/, keys(%{$main::inputs_ref}); |
|
|
| 526 | my $key; |
|
|
| 527 | my @array = (); |
|
|
| 528 | my ($i,$j,$k) = (0,0,0); |
|
|
| 529 | |
|
|
| 530 | #the keys aren't in order, so their info has to be put into the array before doing anything with it |
|
|
| 531 | foreach $key (@keys){ |
|
|
| 532 | # $key =~ /ArRaY\d+\[(\d+),(\d+),(\d+)\]/; |
|
|
| 533 | # ($i,$j,$k) = ($1,$2,$3); |
|
|
| 534 | # $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'['.$i.','.$j.','.$k.']'}; |
|
|
| 535 | $key =~ /ArRaY\d+\_\_(\d+):(\d+):(\d+)\_\_/; |
|
|
| 536 | ($i,$j,$k) = ($1,$2,$3); |
|
|
| 537 | $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'__'.$i.':'.$j.':'.$k.'__'}; |
|
|
| 538 | |
|
|
| 539 | } |
|
|
| 540 | $rh_ans->{debug_student_answer }= \@array; |
|
|
| 541 | my $display_ans = ""; |
|
|
| 542 | |
|
|
| 543 | for( $i=0; $i < scalar(@array) ; $i ++ ) |
|
|
| 544 | { |
|
|
| 545 | $display_ans .= " ["; |
|
|
| 546 | $rh_ans->{preview_text_string} .= ' ['; |
|
|
| 547 | $rh_ans->{preview_latex_string} .= '\begin{pmatrix} '; |
|
|
| 548 | for( $j = 0; $j < scalar( @{$array[$i]} ) ; $j++ ) |
|
|
| 549 | { |
|
|
| 550 | $display_ans .= " ["; |
|
|
| 551 | $rh_ans->{preview_text_string} .= ' ['; |
|
|
| 552 | for( $k = 0; $k < scalar( @{$array[$i][$j]} ) ; $k ++ ){ |
|
|
| 553 | my $entry = $array[$i][$j][$k]; |
|
|
| 554 | $entry = math_constants($entry); |
|
|
| 555 | # This parser code was origianally taken from PGanswermacros::check_syntax |
|
|
| 556 | # but parts of it needed to be slighty modified for this context |
|
|
| 557 | my $parser = new AlgParserWithImplicitExpand; |
|
|
| 558 | my $ret = $parser -> parse($entry); #for use with loops |
|
|
| 559 | |
|
|
| 560 | if ( ref($ret) ) { ## parsed successfully |
|
|
| 561 | $parser -> tostring(); |
|
|
| 562 | $parser -> normalize(); |
|
|
| 563 | $entry = $parser -> tostring(); |
|
|
| 564 | $rh_ans->{preview_text_string} .= $entry.","; |
|
|
| 565 | $rh_ans->{preview_latex_string} .= $parser -> tolatex() . '& '; |
|
|
| 566 | |
|
|
| 567 | } else { ## error in parsing |
|
|
| 568 | $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror}, |
|
|
| 569 | $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg}, |
|
|
| 570 | $rh_ans->{'preview_text_string'} = '', |
|
|
| 571 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR"); |
|
|
| 572 | } |
|
|
| 573 | |
|
|
| 574 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry); |
|
|
| 575 | if ($PG_eval_errors) { |
|
|
| 576 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ; |
|
|
| 577 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
| 578 | last; |
|
|
| 579 | } else { |
|
|
| 580 | $entry = prfmt($inVal,$options{format}); |
|
|
| 581 | $display_ans .= $entry.","; |
|
|
| 582 | $array[$i][$j][$k] = $entry; |
|
|
| 583 | } |
|
|
| 584 | } |
|
|
| 585 | chop($rh_ans->{preview_text_string}); |
|
|
| 586 | chop($display_ans); |
|
|
| 587 | $rh_ans->{preview_text_string} .= '] ,'; |
|
|
| 588 | $rh_ans->{preview_latex_string} .= '\\\\'; |
|
|
| 589 | $display_ans .= '] ,'; |
|
|
| 590 | |
|
|
| 591 | } |
|
|
| 592 | chop($rh_ans->{preview_text_string}); |
|
|
| 593 | chop($display_ans); |
|
|
| 594 | $rh_ans->{preview_text_string} .= '] ,'; |
|
|
| 595 | $rh_ans->{preview_latex_string} .= '\end{pmatrix}'.' , '; |
|
|
| 596 | $display_ans .= '] ,'; |
|
|
| 597 | } |
|
|
| 598 | chop($rh_ans->{preview_text_string}); |
|
|
| 599 | chop($rh_ans->{preview_latex_string}); |
|
|
| 600 | chop($rh_ans->{preview_latex_string}); |
|
|
| 601 | chop($rh_ans->{preview_latex_string}); |
|
|
| 602 | chop($display_ans); |
|
|
| 603 | |
|
|
| 604 | my @temp = (); |
|
|
| 605 | for( $i = 0 ; $i < scalar( @array ); $i++ ){ |
|
|
| 606 | push @temp , display_matrix($array[$i], 'left'=>'.', 'right'=>'.'); |
|
|
| 607 | push @temp , "," unless $i == scalar(@array) - 1; |
|
|
| 608 | } |
|
|
| 609 | $rh_ans->{student_ans} = mbox(\@temp); |
|
|
| 610 | $rh_ans->{ra_student_ans} = \@array; |
|
|
| 611 | |
|
|
| 612 | $rh_ans; |
|
|
| 613 | |
|
|
| 614 | } |
|
|
| 615 | |
|
|
| 616 | |
|
|
| 617 | sub are_orthogonal_vecs{ |
|
|
| 618 | my ($vec_ref , %opts) = @_; |
|
|
| 619 | $vec_ref->{_filter_name} = 'are_orthogonal_vecs'; |
|
|
| 620 | my @vecs = (); |
|
|
| 621 | if( ref($vec_ref) eq 'AnswerHash' ) |
|
|
| 622 | { |
|
|
| 623 | @vecs = @{$vec_ref->{ra_student_ans}}; |
|
|
| 624 | }else{ |
|
|
| 625 | @vecs = @{$vec_ref}; |
|
|
| 626 | } |
|
|
| 627 | my ($i,$j) = (0,0); |
|
|
| 628 | |
|
|
| 629 | my $num = scalar(@vecs); |
|
|
| 630 | my $length = $vecs[0]->[1]; |
|
|
| 631 | |
|
|
| 632 | for( ; $i < $num ; $i ++ ) |
|
|
| 633 | { |
|
|
| 634 | for( $j = $i+1; $j < $num ; $j++ ) |
|
|
| 635 | { |
|
|
| 636 | if( $vecs[$i]->scalar_product($vecs[$j]) > $main::functZeroLevelTolDefault ) |
|
|
| 637 | { |
|
|
| 638 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
| 639 | $vec_ref->{score} = 0; |
|
|
| 640 | if( $vec_ref->{help} =~ /orthogonal|orthonormal|verbose/ ) |
|
|
| 641 | { |
|
|
| 642 | $vec_ref->throw_error('EVAL','You have entered vectors which are not orthogonal. '); |
|
|
| 643 | }else{ |
|
|
| 644 | $vec_ref->throw_error('EVAL'); |
|
|
| 645 | } |
|
|
| 646 | return $vec_ref; |
|
|
| 647 | }else{ |
|
|
| 648 | return 0; |
|
|
| 649 | } |
|
|
| 650 | } |
|
|
| 651 | } |
|
|
| 652 | } |
|
|
| 653 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
| 654 | $vec_ref->{score} = 1; |
|
|
| 655 | $vec_ref; |
|
|
| 656 | }else{ |
|
|
| 657 | 1; |
|
|
| 658 | } |
|
|
| 659 | } |
|
|
| 660 | |
|
|
| 661 | sub is_diagonal{ |
|
|
| 662 | my $matrix = shift; |
|
|
| 663 | my %options = @_; |
|
|
| 664 | my $process_ans_hash = ( ref( $matrix ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
| 665 | my ($rh_ans); |
|
|
| 666 | if ($process_ans_hash) { |
|
|
| 667 | $rh_ans = $matrix; |
|
|
| 668 | $matrix = $rh_ans->{ra_student_ans}; |
|
|
| 669 | } |
|
|
| 670 | |
|
|
| 671 | return 0 unless defined($matrix); |
|
|
| 672 | |
|
|
| 673 | if( ref($matrix) eq 'ARRAY' ){ |
|
|
| 674 | my @matrix = @{$matrix}; |
|
|
| 675 | @matrix = @{$matrix[0]} if ref($matrix[0][0]) eq 'ARRAY'; |
|
|
| 676 | if( ref($matrix[0]) ne 'ARRAY' or scalar( @matrix ) != scalar( @{$matrix[0]} ) ){ |
|
|
| 677 | warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem."; |
|
|
| 678 | } |
|
|
| 679 | |
|
|
| 680 | for( my $i = 0; $i < scalar( @matrix ) ; $i++ ){ |
|
|
| 681 | for( my $j = 0; $j < scalar( @{$matrix[0]} ); $j++ ){ |
|
|
| 682 | if( $matrix[$i][$j] != 0 and $i != $j ) |
|
|
| 683 | { |
|
|
| 684 | if ($process_ans_hash){ |
|
|
| 685 | $rh_ans->throw_error('EVAL'); |
|
|
| 686 | return $rh_ans; |
|
|
| 687 | } else { |
|
|
| 688 | return 0; |
|
|
| 689 | } |
|
|
| 690 | } |
|
|
| 691 | } |
|
|
| 692 | } |
|
|
| 693 | if ($process_ans_hash){ |
|
|
| 694 | return $rh_ans; |
|
|
| 695 | } else { |
|
|
| 696 | return 1; |
|
|
| 697 | } |
|
|
| 698 | }elsif( ref($matrix) eq 'Matrix' ){ |
|
|
| 699 | if( $matrix->[1] != $matrix->[2] ){ |
|
|
| 700 | warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem."; |
|
|
| 701 | if ($process_ans_hash){ |
|
|
| 702 | $rh_ans->throw_error('EVAL'); |
|
|
| 703 | return $rh_ans; |
|
|
| 704 | } else { |
|
|
| 705 | return 0; |
|
|
| 706 | } |
|
|
| 707 | } |
|
|
| 708 | for( my $i = 0; $i < $matrix->[1] ; $i++ ){ |
|
|
| 709 | for( my $j = 0; $j < $matrix->[2] ; $j++ ){ |
|
|
| 710 | if( $matrix->[0][$i][$j] != 0 and $i != $j ){ |
|
|
| 711 | if ($process_ans_hash){ |
|
|
| 712 | $rh_ans->throw_error('EVAL'); |
|
|
| 713 | return $rh_ans; |
|
|
| 714 | } else { |
|
|
| 715 | return 0; |
|
|
| 716 | } |
|
|
| 717 | } |
|
|
| 718 | } |
|
|
| 719 | } |
|
|
| 720 | if ($process_ans_hash){ |
|
|
| 721 | return $rh_ans; |
|
|
| 722 | } else { |
|
|
| 723 | return 1; |
|
|
| 724 | } |
|
|
| 725 | }else{ |
|
|
| 726 | warn "There is a problem with the problem, please alert your professor."; |
|
|
| 727 | if ($process_ans_hash){ |
|
|
| 728 | $rh_ans->throw_error('EVAL'); |
|
|
| 729 | return $rh_ans; |
|
|
| 730 | } else { |
|
|
| 731 | return 0; |
|
|
| 732 | } |
|
|
| 733 | } |
|
|
| 734 | |
|
|
| 735 | } |
|
|
| 736 | |
|
|
| 737 | |
|
|
| 738 | sub are_unit_vecs{ |
|
|
| 739 | my ( $vec_ref,%opts ) = @_; |
|
|
| 740 | $vec_ref->{_filter_name} = 'are_unit_vecs'; |
|
|
| 741 | my @vecs = (); |
|
|
| 742 | if( ref($vec_ref) eq 'AnswerHash' ) |
|
|
| 743 | { |
|
|
| 744 | @vecs = @{$vec_ref->{ra_student_ans}}; |
|
|
| 745 | }else{ |
|
|
| 746 | @vecs = @{$vec_ref}; |
|
|
| 747 | } |
|
|
| 748 | |
|
|
| 749 | my $i = 0; |
|
|
| 750 | my $num = scalar(@vecs); |
|
|
| 751 | my $length = $vecs[0]->[1]; |
|
|
| 752 | |
|
|
| 753 | for( ; $i < $num ; $i ++ ) |
|
|
| 754 | { |
|
|
| 755 | if( abs(sqrt($vecs[$i]->scalar_product($vecs[$i]))- 1) > $main::functZeroLevelTolDefault ) |
|
|
| 756 | { |
|
|
| 757 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
| 758 | $vec_ref->{score} = 0; |
|
|
| 759 | if( $vec_ref->{help} =~ /unit|orthonormal|verbose/ ) |
|
|
| 760 | { |
|
|
| 761 | $vec_ref->throw_error('EVAL','You have entered vector(s) which are not of unit length.'); |
|
|
| 762 | }else{ |
|
|
| 763 | $vec_ref->throw_error('EVAL'); |
|
|
| 764 | } |
|
|
| 765 | return $vec_ref; |
|
|
| 766 | }else{ |
|
|
| 767 | return 0; |
|
|
| 768 | } |
|
|
| 769 | |
|
|
| 770 | } |
|
|
| 771 | } |
|
|
| 772 | |
|
|
| 773 | if( ref( $vec_ref ) eq 'AnswerHash' ){ |
|
|
| 774 | $vec_ref->{score} = 1; |
|
|
| 775 | $vec_ref; |
|
|
| 776 | }else{ |
|
|
| 777 | 1; |
|
|
| 778 | } |
|
|
| 779 | } |
|
|
| 780 | |
|
|
| 781 | sub display_correct_vecs{ |
|
|
| 782 | my ( $ra_vecs,%opts ) = @_; |
|
|
| 783 | my @ra_vecs = @{$ra_vecs}; |
|
|
| 784 | my @temp = (); |
|
|
| 785 | |
|
|
| 786 | for( my $i = 0 ; $i < scalar(@ra_vecs) ; $i++ ){ |
|
|
| 787 | push @temp, display_matrix(Matrix->new_from_col_vecs([$ra_vecs[$i]]),'left'=>'.','right'=>'.'); |
|
|
| 788 | push @temp, ","; |
|
|
| 789 | } |
|
|
| 790 | |
|
|
| 791 | pop @temp; |
|
|
| 792 | |
|
|
| 793 | mbox(\@temp); |
|
|
| 794 | |
|
|
| 795 | } |
|
|
| 796 | |
|
|
| 797 | sub vec_solution_cmp{ |
|
|
| 798 | my $correctAnswer = shift; |
|
|
| 799 | my %opt = @_; |
|
|
| 800 | |
|
|
| 801 | set_default_options( \%opt, |
|
|
| 802 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
|
|
| 803 | 'debug' => 0, |
|
|
| 804 | 'mode' => 'basis', |
|
|
| 805 | 'help' => 'none', |
|
|
| 806 | ); |
|
|
| 807 | |
|
|
| 808 | $opt{debug} = 0 unless defined($opt{debug}); |
|
|
| 809 | |
|
|
| 810 | ## This is where the correct answer should be checked someday. |
|
|
| 811 | my $matrix = Matrix->new_from_col_vecs($correctAnswer); |
|
|
| 812 | |
|
|
| 813 | |
|
|
| 814 | #construct the answer evaluator |
|
|
| 815 | my $answer_evaluator = new AnswerEvaluator; |
|
|
| 816 | |
|
|
| 817 | $answer_evaluator->{debug} = $opt{debug}; |
|
|
| 818 | $answer_evaluator->ans_hash( correct_ans => display_correct_vecs($correctAnswer), |
|
|
| 819 | old_correct_ans => $correctAnswer, |
|
|
| 820 | rm_correct_ans => $matrix, |
|
|
| 821 | zeroLevelTol => $opt{zeroLevelTol}, |
|
|
| 822 | debug => $opt{debug}, |
|
|
| 823 | mode => $opt{mode}, |
|
|
| 824 | help => $opt{help}, |
|
|
| 825 | ); |
|
|
| 826 | |
|
|
| 827 | $answer_evaluator->install_pre_filter(\&ans_array_filter); |
|
|
| 828 | $answer_evaluator->install_pre_filter(sub{ |
|
|
| 829 | my ($rh_ans,@options) = @_; |
|
|
| 830 | my @student_array = @{$rh_ans->{ra_student_ans}}; |
|
|
| 831 | my @array = (); |
|
|
| 832 | for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) |
|
|
| 833 | { |
|
|
| 834 | push( @array, Matrix->new_from_array_ref($student_array[$i])); |
|
|
| 835 | } |
|
|
| 836 | $rh_ans->{ra_student_ans} = \@array; |
|
|
| 837 | $rh_ans; |
|
|
| 838 | });#ra_student_ans is now the students answer as an array of vectors |
|
|
| 839 | # anonymous subroutine to check dimension and length of the student vectors |
|
|
| 840 | # if either is wrong, the answer is wrong. |
|
|
| 841 | $answer_evaluator->install_pre_filter(sub{ |
|
|
| 842 | my $rh_ans = shift; |
|
|
| 843 | my $length = $rh_ans->{rm_correct_ans}->[1]; |
|
|
| 844 | my $dim = $rh_ans->{rm_correct_ans}->[2]; |
|
|
| 845 | if( $dim != scalar(@{$rh_ans->{ra_student_ans}})) |
|
|
| 846 | { |
|
|
| 847 | |
|
|
| 848 | $rh_ans->{score} = 0; |
|
|
| 849 | if( $rh_ans->{help} =~ /dim|verbose/ ) |
|
|
| 850 | { |
|
|
| 851 | $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.'); |
|
|
| 852 | }else{ |
|
|
| 853 | $rh_ans->throw_error('EVAL'); |
|
|
| 854 | } |
|
|
| 855 | } |
|
|
| 856 | for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) |
|
|
| 857 | { |
|
|
| 858 | if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) |
|
|
| 859 | { |
|
|
| 860 | $rh_ans->{score} = 0; |
|
|
| 861 | if( $rh_ans->{help} =~ /length|verbose/ ) |
|
|
| 862 | { |
|
|
| 863 | $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.'); |
|
|
| 864 | }else{ |
|
|
| 865 | $rh_ans->throw_error('EVAL'); |
|
|
| 866 | } |
|
|
| 867 | } |
|
|
| 868 | } |
|
|
| 869 | $rh_ans; |
|
|
| 870 | }); |
|
|
| 871 | # Install prefilter for various modes |
|
|
| 872 | if( $opt{mode} ne 'basis' ) |
|
|
| 873 | { |
|
|
| 874 | if( $opt{mode} =~ /orthogonal|orthonormal/ ) |
|
|
| 875 | { |
|
|
| 876 | $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs); |
|
|
| 877 | } |
|
|
| 878 | |
|
|
| 879 | if( $opt{mode} =~ /unit|orthonormal/ ) |
|
|
| 880 | { |
|
|
| 881 | $answer_evaluator->install_pre_filter(\&are_unit_vecs); |
|
|
| 882 | |
|
|
| 883 | } |
|
|
| 884 | } |
|
|
| 885 | |
880 | } |
|
|
881 | |
| 886 | $answer_evaluator->install_evaluator(\&compare_vec_solution, %opt); |
882 | $answer_evaluator->install_evaluator(\&compare_vec_solution, %opt); |
| 887 | |
883 | |
| 888 | $answer_evaluator->install_post_filter( |
884 | $answer_evaluator->install_post_filter( |
| 889 | sub {my $rh_ans = shift; |
885 | sub {my $rh_ans = shift; |
| 890 | if ($rh_ans->catch_error('SYNTAX') ) { |
886 | if ($rh_ans->catch_error('SYNTAX') ) { |
| 891 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
887 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
| 892 | $rh_ans->clear_error('SYNTAX'); |
888 | $rh_ans->clear_error('SYNTAX'); |
| 893 | } |
889 | } |
| 894 | if ($rh_ans->catch_error('EVAL') ) { |
890 | if ($rh_ans->catch_error('EVAL') ) { |
| 895 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
891 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
| 896 | $rh_ans->clear_error('EVAL'); |
892 | $rh_ans->clear_error('EVAL'); |
| 897 | } |
893 | } |
| 898 | $rh_ans; |
894 | $rh_ans; |
| 899 | } |
895 | } |
| 900 | ); |
896 | ); |
| 901 | $answer_evaluator; |
897 | $answer_evaluator; |
| 902 | |
898 | |
| 903 | } |
899 | } |
| 904 | |
900 | |
| 905 | |
901 | |
| 906 | sub compare_vec_solution { |
902 | sub compare_vec_solution { |
| 907 | my ( $rh_ans, %options ) = @_ ; |
903 | my ( $rh_ans, %options ) = @_ ; |
|
|
904 | $rh_ans->{_filter_name} = "compare_vec_solution"; |
| 908 | my @space = @{$rh_ans->{ra_student_ans}}; |
905 | my @space = @{$rh_ans->{ra_student_ans}}; |
| 909 | my $solution = shift @space; |
906 | my $solution = shift @space; |
| 910 | |
907 | |
| 911 | # A lot of the follosing code was taken from Matrix::proj_coeff |
908 | # A lot of the following code was taken from Matrix::proj_coeff |
| 912 | # calling this method recursively would be a waste of time since |
909 | # calling this method recursively would be a waste of time since |
| 913 | # the prof's matrix never changes and solve_LR is an expensive |
910 | # the prof's matrix never changes and solve_LR is an expensive |
| 914 | # operation. This way it is only done once. |
911 | # operation. This way it is only done once. |
| 915 | my $matrix = $rh_ans->{rm_correct_ans}; |
912 | my $matrix = $rh_ans->{rm_correct_ans}; |
| 916 | my ($dim,$x_vector, $base_matrix); |
913 | my ($dim,$x_vector, $base_matrix); |
| 917 | my $errors = undef; |
914 | my $errors = undef; |
| 918 | my $lin_space_tr= ~ $matrix; |
915 | my $lin_space_tr= ~ $matrix; |
| 919 | $matrix = $lin_space_tr * $matrix; |
916 | $matrix = $lin_space_tr * $matrix; |
| 920 | my $matrix_lr = $matrix->decompose_LR(); |
917 | my $matrix_lr = $matrix->decompose_LR(); |
| 921 | |
918 | |
| 922 | #this section determines whether or not the first vector, a solution to |
919 | #this section determines whether or not the first vector, a solution to |
| 923 | #the system, is a linear combination of the prof's vectors in which there |
920 | #the system, is a linear combination of the prof's vectors in which there |
| 924 | #is a nonzero coefficient on the first term, the prof's solution to the system |
921 | #is a nonzero coefficient on the first term, the prof's solution to the system |
| 925 | $solution = $lin_space_tr*$solution; |
922 | $solution = $lin_space_tr*$solution; |
| 926 | ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($solution); |
923 | ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($solution); |
|
|
924 | #$rh_ans->{debug_compare_vec_solution} = $x_vector->element(1,1); |
| 927 | if( $dim ){ |
925 | if( $dim ){ |
| 928 | $rh_ans->throw_error('EVAL', "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" ); # only print if the dim is not zero. |
926 | $rh_ans->throw_error('EVAL', "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" ); # only print if the dim is not zero. |
| 929 | $rh_ans->{score} = 0; |
927 | $rh_ans->{score} = 0; |
| 930 | $rh_ans; |
928 | $rh_ans; |
| 931 | }elsif( abs($x_vector->[0][0][0]) <= $options{zeroLevelTol} ) |
929 | } elsif( abs($x_vector->element(1,1) -1) >= $options{zeroLevelTol} ) { |
| 932 | { |
930 | # changes by MEG 6/24/05 |
|
|
931 | # the student answer needs to be a linear combination of the instructors vectors |
|
|
932 | # and the coefficient of the first vector needs to be 1 (it is NOT enough that it be non-zero). |
|
|
933 | # if this is not the case, then the answer is wrong. |
|
|
934 | # replaced $x_vector->[0][0][0] by $x_vector->element(1,1) since this doesn't depend on the internal structure of the matrix object. |
|
|
935 | |
| 933 | $rh_ans->{score} = 0; |
936 | $rh_ans->{score} = 0; |
| 934 | $rh_ans; |
937 | $rh_ans; |
| 935 | }else{ |
938 | } else { |
| 936 | $rh_ans->{score} = 1; |
939 | $rh_ans->{score} = 1; |
| 937 | my @correct_space = @{$rh_ans->{old_correct_ans}}; |
940 | my @correct_space = @{$rh_ans->{old_correct_ans}}; |
| 938 | shift @correct_space; |
941 | shift @correct_space; |
| 939 | $rh_ans->{rm_correct_ans} = Matrix->new_from_col_vecs(\@correct_space); |
942 | $rh_ans->{rm_correct_ans} = Matrix->new_from_col_vecs(\@correct_space); |
| 940 | $rh_ans->{ra_student_ans} = \@space; |
943 | $rh_ans->{ra_student_ans} = \@space; |
| 941 | return compare_basis( $rh_ans, %options ); |
944 | return compare_basis( $rh_ans, %options ); |
| 942 | } |
945 | } |
| 943 | } |
946 | } |
| 944 | |
947 | |
| 945 | 1; |
948 | 1; |