|
1 | | -(* A function to test whether a response is equal to an answer, \ |
2 | | -including to within a given tolerance; input and output as |
3 | | -Associations *) |
4 | | - |
5 | | -equalQAssociation = |
6 | | - Function[ |
7 | | - Module[{tolerance, correctQ, error}, |
8 | | - If[NumericQ[#answer], |
9 | | - tolerance = |
10 | | - If[#params["tolerance_is_absolute"], |
11 | | - #params["tolerance"] |
12 | | - , |
13 | | - #params["tolerance"] * #params["answer"] |
14 | | - ]; |
15 | | - error = Abs[#answer - #response]; |
16 | | - correctQ = TrueQ[error <= tolerance] |
17 | | - , |
18 | | - error = "not applicable"; |
19 | | - correctQ = TrueQ[#answer == #response] |
20 | | - ]; |
21 | | - <| |
22 | | - "command" -> "eval" |
23 | | - , |
24 | | - "result" -> |
25 | | - { |
26 | | - "is_correct" -> correctQ |
27 | | - , |
28 | | - "feedback" -> |
29 | | - If[correctQ, |
30 | | - #params["correct_response_feedback"] |
31 | | - , |
32 | | - #params["incorrect_response_feedback" |
33 | | - ] |
34 | | - ] |
35 | | - , |
36 | | - "error" -> error |
37 | | - } |
38 | | - |> |
39 | | - ] |
40 | | - ]; |
41 | | - |
42 | | -(* A function to test whether a response is equal to an answer, \ |
43 | | -including to within a given tolerance; input and output as |
44 | | -JSON strings |
45 | | -
|
46 | | -Calls equalQAssociation *) |
47 | | - |
48 | | -equalQJSON = Function[ExportString[equalQAssociation[ImportString[#, |
49 | | - "JSON"] //. List :> Association], "JSON", "Compact" -> True]]; |
50 | | - |
51 | | -(* A function to test whether a response is equal to an answer, \ |
52 | | -including to within a given tolerance; input and output read in from \ |
53 | | -external JSON files |
54 | | -
|
55 | | -Calls equalQAssociation *) |
56 | | - |
57 | | -equalQIO = Function[Export[#2, equalQAssociation[Import[#1, "JSON"] //. |
58 | | - List :> Association], "JSON", "Compact" -> True]]; |
59 | | - |
60 | | -argv = Rest[$ScriptCommandLine] |
61 | | - |
62 | | -equalQIO[argv[[1]], argv[[2]]] |
| 1 | +(* ::Package:: *) |
| 2 | + |
| 3 | +(* The basic evaluation function code*) |
| 4 | + |
| 5 | +equalQNumeric[answer_, response_, params_] := Module[{tolerance}, |
| 6 | + tolerance = If[Lookup[params, "tolerance_is_absolute", False], |
| 7 | + Lookup[params, "tolerance", 0], |
| 8 | + Lookup[params, "tolerance", 0] * answer |
| 9 | + ]; |
| 10 | + error = Abs[answer - response]; |
| 11 | + <| |
| 12 | + "error" -> error, |
| 13 | + "is_correct" -> TrueQ[error <= tolerance] |
| 14 | + |> |
| 15 | +] |
| 16 | + |
| 17 | +equalQOther[answer_, response_, params_] := Module[{correctQ}, |
| 18 | + <| |
| 19 | + "error" -> Null, |
| 20 | + "is_correct" -> TrueQ[answer == response] |
| 21 | + |> |
| 22 | +]; |
| 23 | + |
| 24 | +(* Patternize: a function that takes an expression and a list of \ |
| 25 | +named variables, and converts all unnamed symbols in the expression \ |
| 26 | +into Optional[..] patterns *) |
| 27 | + |
| 28 | +Options[PatternizeSymbol] = {Atomic -> False}; |
| 29 | + |
| 30 | +PatternizeSymbol[a_Symbol, namedVariables_, OptionsPattern[]] /; |
| 31 | + Not[MemberQ[namedVariables, a]] := \!\(\* |
| 32 | +TagBox[ |
| 33 | +StyleBox[ |
| 34 | +RowBox[{"If", "[", |
| 35 | +RowBox[{ |
| 36 | +RowBox[{"OptionValue", "[", "Atomic", "]"}], ",", |
| 37 | +RowBox[{"(", |
| 38 | +RowBox[{"Optional", "[", |
| 39 | +RowBox[{"PatternTest", "[", |
| 40 | +RowBox[{ |
| 41 | +RowBox[{"pattern", "[", |
| 42 | +RowBox[{"a", ",", |
| 43 | +RowBox[{"Blank", "[", "]"}]}], "]"}], ",", "AtomQ"}], "]"}], "]"}], ")"}], ",", |
| 44 | +RowBox[{"(", |
| 45 | +RowBox[{"Optional", "[", |
| 46 | +RowBox[{"pattern", "[", |
| 47 | +RowBox[{"a", ",", |
| 48 | +RowBox[{"Blank", "[", "]"}]}], "]"}], "]"}], ")"}]}], "]"}], |
| 49 | +ShowSpecialCharacters->False, |
| 50 | +ShowStringCharacters->True, |
| 51 | +NumberMarks->True], |
| 52 | +FullForm]\) /. pattern -> Pattern |
| 53 | + |
| 54 | +PatternizeSymbol[a_, namedVariables_, OptionsPattern[]] := a |
| 55 | + |
| 56 | +ComplexResolve[Optional[a_Symbol] + I Optional[b_Symbol]] := |
| 57 | + Complex[a, b] |
| 58 | + |
| 59 | +ComplexResolve[I Optional[b_Symbol]*Pi] := Complex[0, b]*Pi |
| 60 | + |
| 61 | +ComplexResolve[Complex[0, Optional[b_Symbol]] + Optional[a_Symbol]] := |
| 62 | + Complex[a, b] |
| 63 | + |
| 64 | +ComplexResolve[a_] := a |
| 65 | + |
| 66 | +DepatternizePattern[pattern_Optional] := pattern[[1, 1, 1]] |
| 67 | + |
| 68 | +DepatternizePattern[pattern_] := pattern |
| 69 | + |
| 70 | +ComplexResolve[Optional[a_Symbol] + I Optional[b_Symbol]] := |
| 71 | + Complex[a, b] |
| 72 | + |
| 73 | +ComplexResolve[I Optional[b_Symbol]*Pi] := Complex[0, b]*Pi |
| 74 | + |
| 75 | +ComplexResolve[Complex[0, Optional[b_Symbol]] + Optional[a_Symbol]] := |
| 76 | + Complex[a, b] |
| 77 | + |
| 78 | +ComplexResolve[a_] := a |
| 79 | + |
| 80 | +Options[Patternize] = {Atomic -> False}; |
| 81 | + |
| 82 | +Patternize[expression_, namedVariables_, OptionsPattern[]] := |
| 83 | + Map[PatternizeSymbol[#, namedVariables, |
| 84 | + Atomic -> OptionValue[Atomic]] &, |
| 85 | + MapAll[ComplexResolve, expression], {-1}] |
| 86 | + |
| 87 | +Depatternize[pattern_] := MapAll[DepatternizePattern, pattern] |
| 88 | + |
| 89 | +(*StructureMatchQ: a function that checks whether a user's response \ |
| 90 | +has the same structure as a given answer template, given a set of \ |
| 91 | +named variables.*) |
| 92 | + |
| 93 | +inertFunctionRules = {Sin -> fSin, Cos -> fCos, Tan -> fTan, |
| 94 | + Sec -> fSec, Csc -> fCsc, Cot -> fCot, ArcSin -> fArcSin, |
| 95 | + ArcCos -> fArcCos, ArcTan -> fArcTan, ArcSec -> fArcSec, |
| 96 | + ArcCsc -> fArcCsc, ArcCot -> fArcCot, Sinh -> fSinh, Cosh -> fCosh, |
| 97 | + Tanh -> fTanh, Sech -> fSech, Csch -> fCsch, Coth -> fCoth, |
| 98 | + ArcSinh -> fArcSinh, ArcCosh -> fArcCosh, ArcTanh -> fArcTanh, |
| 99 | + ArcSech -> fArcSech, ArcCsch -> fArcCsch, ArcCoth -> fArcCoth, |
| 100 | + Exp -> fExp, Log -> fLog}; |
| 101 | + |
| 102 | +Options[StructureMatchQ] = {Atomic -> False}; |
| 103 | + |
| 104 | +StructureMatchQ[response_, answerTemplate_, namedVariables_, |
| 105 | + OptionsPattern[]] := |
| 106 | + Module[{response2, answerTemplate2}, |
| 107 | + response2 = ReplaceAll[response, inertFunctionRules]; |
| 108 | + answerTemplate2 = ReplaceAll[answerTemplate, inertFunctionRules]; |
| 109 | + MatchQ[response2, |
| 110 | + Patternize[answerTemplate2, namedVariables, |
| 111 | + Atomic -> OptionValue[Atomic]]]] |
| 112 | + |
| 113 | +equalQStructure[answer_, response_, params_] := Module[{namedVariables,correctQ}, |
| 114 | + namedVariables = ToExpression[Lookup[params,"named_variables",{}],TraditionalForm]; |
| 115 | + correctQ = StructureMatchQ[ |
| 116 | + ToExpression[ToString[response],TraditionalForm], |
| 117 | + ToExpression[ToString[answer],TraditionalForm], |
| 118 | + namedVariables]; |
| 119 | + <| |
| 120 | + "error" -> Null, |
| 121 | + "is_correct" -> correctQ |
| 122 | + |> |
| 123 | +] |
| 124 | + |
| 125 | +(* The evaluation function itself *) |
| 126 | + |
| 127 | +evalQ[answer_, response_, params_] := Module[{}, |
| 128 | + Which[ |
| 129 | + Lookup[params,"equality_test","None"] == "structure", |
| 130 | + equalQStructure[answer,response,params], |
| 131 | + NumericQ[answer], |
| 132 | + equalQNumeric[answer, response, params], |
| 133 | + True, |
| 134 | + equalQOther[answer, response, params] |
| 135 | + ] |
| 136 | +]; |
| 137 | + |
| 138 | +EvaluationFunction[answer_, response_, params_] := Module[{tolerance, correctQ, error}, |
| 139 | + result = evalQ[answer, response, params]; |
| 140 | + <| |
| 141 | + "is_correct" -> result["is_correct"], |
| 142 | + "feedback" -> If[result["is_correct"], |
| 143 | + Lookup[params, "correct_response_feedback", "Correct!"], |
| 144 | + Lookup[params, "incorrect_response_feedback", "Incorrect!"] |
| 145 | + ], |
| 146 | + "error" -> result["error"] |
| 147 | + |> |
| 148 | +]; |
0 commit comments