QuizGenerator 0.4.3__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QuizGenerator/contentast.py +949 -80
- QuizGenerator/generate.py +44 -7
- QuizGenerator/misc.py +4 -554
- QuizGenerator/mixins.py +47 -25
- QuizGenerator/premade_questions/cst334/languages.py +139 -125
- QuizGenerator/premade_questions/cst334/math_questions.py +78 -66
- QuizGenerator/premade_questions/cst334/memory_questions.py +258 -144
- QuizGenerator/premade_questions/cst334/persistence_questions.py +71 -33
- QuizGenerator/premade_questions/cst334/process.py +51 -20
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +32 -6
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +59 -34
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +27 -8
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +53 -32
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +228 -88
- QuizGenerator/premade_questions/cst463/models/attention.py +26 -10
- QuizGenerator/premade_questions/cst463/models/cnns.py +32 -19
- QuizGenerator/premade_questions/cst463/models/rnns.py +25 -12
- QuizGenerator/premade_questions/cst463/models/text.py +26 -11
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +36 -22
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +89 -109
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +126 -53
- QuizGenerator/question.py +110 -15
- QuizGenerator/quiz.py +74 -23
- QuizGenerator/regenerate.py +98 -29
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/RECORD +29 -31
- QuizGenerator/README.md +0 -5
- QuizGenerator/logging.yaml +0 -55
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,6 +15,11 @@ from .misc import generate_function, format_vector
|
|
|
15
15
|
log = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
# Note: This file does not use ContentAST.Answer wrappers - it uses TableQuestionMixin
|
|
19
|
+
# which handles answer display through create_answer_table(). The answers are created
|
|
20
|
+
# with labels embedded at creation time in refresh().
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
class GradientDescentQuestion(Question, abc.ABC):
|
|
19
24
|
def __init__(self, *args, **kwargs):
|
|
20
25
|
kwargs["topic"] = kwargs.get("topic", Question.Topic.ML_OPTIMIZATION)
|
|
@@ -86,30 +91,32 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
86
91
|
|
|
87
92
|
# Set up answers
|
|
88
93
|
self.answers = {}
|
|
89
|
-
|
|
94
|
+
|
|
90
95
|
# Answers for each step
|
|
91
96
|
for i, result in enumerate(self.gradient_descent_results):
|
|
92
97
|
step = result['step']
|
|
93
|
-
|
|
98
|
+
|
|
94
99
|
# Location answer
|
|
95
100
|
location_key = f"answer__location_{step}"
|
|
96
|
-
self.answers[location_key] = Answer.vector_value(location_key, list(result['location']))
|
|
97
|
-
|
|
101
|
+
self.answers[location_key] = Answer.vector_value(location_key, list(result['location']), label=f"Location at step {step}")
|
|
102
|
+
|
|
98
103
|
# Gradient answer
|
|
99
104
|
gradient_key = f"answer__gradient_{step}"
|
|
100
|
-
self.answers[gradient_key] = Answer.vector_value(gradient_key, list(result['gradient']))
|
|
101
|
-
|
|
105
|
+
self.answers[gradient_key] = Answer.vector_value(gradient_key, list(result['gradient']), label=f"Gradient at step {step}")
|
|
106
|
+
|
|
102
107
|
# Update answer
|
|
103
108
|
update_key = f"answer__update_{step}"
|
|
104
|
-
self.answers[update_key] = Answer.vector_value(update_key, list(result['update']))
|
|
109
|
+
self.answers[update_key] = Answer.vector_value(update_key, list(result['update']), label=f"Update at step {step}")
|
|
105
110
|
|
|
106
|
-
def
|
|
111
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
112
|
+
"""Build question body and collect answers."""
|
|
107
113
|
body = ContentAST.Section()
|
|
108
|
-
|
|
114
|
+
answers = []
|
|
115
|
+
|
|
109
116
|
# Introduction
|
|
110
117
|
objective = "minimize" if self.minimize else "maximize"
|
|
111
118
|
sign = "-" if self.minimize else "+"
|
|
112
|
-
|
|
119
|
+
|
|
113
120
|
body.add_element(
|
|
114
121
|
ContentAST.Paragraph(
|
|
115
122
|
[
|
|
@@ -122,7 +129,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
122
129
|
]
|
|
123
130
|
)
|
|
124
131
|
)
|
|
125
|
-
|
|
132
|
+
|
|
126
133
|
# Create table data - use ContentAST.Equation for proper LaTeX rendering in headers
|
|
127
134
|
headers = [
|
|
128
135
|
"n",
|
|
@@ -131,36 +138,49 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
131
138
|
ContentAST.Equation("\\alpha \\cdot \\nabla f", inline=True)
|
|
132
139
|
]
|
|
133
140
|
table_rows = []
|
|
134
|
-
|
|
141
|
+
|
|
135
142
|
for i in range(self.num_steps):
|
|
136
143
|
step = i + 1
|
|
137
144
|
row = {"n": str(step)}
|
|
138
|
-
|
|
145
|
+
|
|
139
146
|
if step == 1:
|
|
140
|
-
|
|
147
|
+
|
|
141
148
|
# Fill in starting location for first row with default formatting
|
|
142
149
|
row["location"] = f"{format_vector(self.starting_point)}"
|
|
143
150
|
row[headers[2]] = f"answer__gradient_{step}" # gradient column
|
|
144
151
|
row[headers[3]] = f"answer__update_{step}" # update column
|
|
152
|
+
# Collect answers for this step (no location answer for step 1)
|
|
153
|
+
answers.append(self.answers[f"answer__gradient_{step}"])
|
|
154
|
+
answers.append(self.answers[f"answer__update_{step}"])
|
|
145
155
|
else:
|
|
146
156
|
# Subsequent rows - all answer fields
|
|
147
157
|
row["location"] = f"answer__location_{step}"
|
|
148
158
|
row[headers[2]] = f"answer__gradient_{step}" # gradient column
|
|
149
159
|
row[headers[3]] = f"answer__update_{step}" # update column
|
|
160
|
+
# Collect all answers for this step
|
|
161
|
+
answers.append(self.answers[f"answer__location_{step}"])
|
|
162
|
+
answers.append(self.answers[f"answer__gradient_{step}"])
|
|
163
|
+
answers.append(self.answers[f"answer__update_{step}"])
|
|
150
164
|
table_rows.append(row)
|
|
151
|
-
|
|
165
|
+
|
|
152
166
|
# Create the table using mixin
|
|
153
167
|
gradient_table = self.create_answer_table(
|
|
154
168
|
headers=headers,
|
|
155
169
|
data_rows=table_rows,
|
|
156
170
|
answer_columns=["location", headers[2], headers[3]] # Use actual header objects
|
|
157
171
|
)
|
|
158
|
-
|
|
172
|
+
|
|
159
173
|
body.add_element(gradient_table)
|
|
160
|
-
|
|
174
|
+
|
|
175
|
+
return body, answers
|
|
176
|
+
|
|
177
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
178
|
+
"""Build question body (backward compatible interface)."""
|
|
179
|
+
body, _ = self._get_body(**kwargs)
|
|
161
180
|
return body
|
|
162
181
|
|
|
163
|
-
def
|
|
182
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
183
|
+
"""Build question explanation."""
|
|
164
184
|
explanation = ContentAST.Section()
|
|
165
185
|
|
|
166
186
|
explanation.add_element(
|
|
@@ -201,7 +221,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
201
221
|
]
|
|
202
222
|
)
|
|
203
223
|
)
|
|
204
|
-
|
|
224
|
+
|
|
205
225
|
# Add completed table showing all solutions
|
|
206
226
|
explanation.add_element(
|
|
207
227
|
ContentAST.Paragraph(
|
|
@@ -210,7 +230,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
210
230
|
]
|
|
211
231
|
)
|
|
212
232
|
)
|
|
213
|
-
|
|
233
|
+
|
|
214
234
|
# Create filled solution table
|
|
215
235
|
solution_headers = [
|
|
216
236
|
"n",
|
|
@@ -218,31 +238,31 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
218
238
|
ContentAST.Equation("\\nabla f", inline=True),
|
|
219
239
|
ContentAST.Equation("\\alpha \\cdot \\nabla f", inline=True)
|
|
220
240
|
]
|
|
221
|
-
|
|
241
|
+
|
|
222
242
|
solution_rows = []
|
|
223
243
|
for i, result in enumerate(self.gradient_descent_results):
|
|
224
244
|
step = result['step']
|
|
225
245
|
row = {"n": str(step)}
|
|
226
|
-
|
|
246
|
+
|
|
227
247
|
row["location"] = f"{format_vector(result['location'])}"
|
|
228
248
|
row[solution_headers[2]] = f"{format_vector(result['gradient'])}"
|
|
229
249
|
row[solution_headers[3]] = f"{format_vector(result['update'])}"
|
|
230
|
-
|
|
250
|
+
|
|
231
251
|
solution_rows.append(row)
|
|
232
|
-
|
|
252
|
+
|
|
233
253
|
# Create solution table (non-answer table, just display)
|
|
234
254
|
solution_table = self.create_answer_table(
|
|
235
255
|
headers=solution_headers,
|
|
236
256
|
data_rows=solution_rows,
|
|
237
257
|
answer_columns=[] # No answer columns since this is just for display
|
|
238
258
|
)
|
|
239
|
-
|
|
259
|
+
|
|
240
260
|
explanation.add_element(solution_table)
|
|
241
|
-
|
|
261
|
+
|
|
242
262
|
# Step-by-step explanation
|
|
243
263
|
for i, result in enumerate(self.gradient_descent_results):
|
|
244
264
|
step = result['step']
|
|
245
|
-
|
|
265
|
+
|
|
246
266
|
explanation.add_element(
|
|
247
267
|
ContentAST.Paragraph(
|
|
248
268
|
[
|
|
@@ -250,7 +270,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
250
270
|
]
|
|
251
271
|
)
|
|
252
272
|
)
|
|
253
|
-
|
|
273
|
+
|
|
254
274
|
explanation.add_element(
|
|
255
275
|
ContentAST.Paragraph(
|
|
256
276
|
[
|
|
@@ -258,7 +278,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
258
278
|
]
|
|
259
279
|
)
|
|
260
280
|
)
|
|
261
|
-
|
|
281
|
+
|
|
262
282
|
explanation.add_element(
|
|
263
283
|
ContentAST.Paragraph(
|
|
264
284
|
[
|
|
@@ -266,7 +286,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
266
286
|
]
|
|
267
287
|
)
|
|
268
288
|
)
|
|
269
|
-
|
|
289
|
+
|
|
270
290
|
explanation.add_element(
|
|
271
291
|
ContentAST.Paragraph(
|
|
272
292
|
[
|
|
@@ -278,13 +298,13 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
278
298
|
]
|
|
279
299
|
)
|
|
280
300
|
)
|
|
281
|
-
|
|
301
|
+
|
|
282
302
|
if step < len(self.gradient_descent_results):
|
|
283
303
|
# Calculate next location for display
|
|
284
304
|
current_loc = result['location']
|
|
285
305
|
update = result['update']
|
|
286
306
|
next_loc = [current_loc[j] - update[j] for j in range(len(current_loc))]
|
|
287
|
-
|
|
307
|
+
|
|
288
308
|
explanation.add_element(
|
|
289
309
|
ContentAST.Paragraph(
|
|
290
310
|
[
|
|
@@ -292,7 +312,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
292
312
|
]
|
|
293
313
|
)
|
|
294
314
|
)
|
|
295
|
-
|
|
315
|
+
|
|
296
316
|
function_values = [r['function_value'] for r in self.gradient_descent_results]
|
|
297
317
|
explanation.add_element(
|
|
298
318
|
ContentAST.Paragraph(
|
|
@@ -301,5 +321,10 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
|
|
|
301
321
|
]
|
|
302
322
|
)
|
|
303
323
|
)
|
|
304
|
-
|
|
324
|
+
|
|
325
|
+
return explanation, []
|
|
326
|
+
|
|
327
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
328
|
+
"""Build question explanation (backward compatible interface)."""
|
|
329
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
305
330
|
return explanation
|
|
@@ -13,6 +13,9 @@ from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
|
13
13
|
log = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
# Note: This file migrates to the _get_body()/_get_explanation() pattern
|
|
17
|
+
|
|
18
|
+
|
|
16
19
|
class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
17
20
|
"""Base class for loss function calculation questions."""
|
|
18
21
|
|
|
@@ -70,14 +73,15 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
70
73
|
|
|
71
74
|
# Individual loss answers
|
|
72
75
|
for i in range(self.num_samples):
|
|
73
|
-
self.answers[f"loss_{i}"] = Answer.float_value(f"loss_{i}", self.individual_losses[i])
|
|
76
|
+
self.answers[f"loss_{i}"] = Answer.float_value(f"loss_{i}", self.individual_losses[i], label=f"Sample {i+1} loss")
|
|
74
77
|
|
|
75
78
|
# Overall loss answer
|
|
76
|
-
self.answers["overall_loss"] = Answer.float_value("overall_loss", self.overall_loss)
|
|
79
|
+
self.answers["overall_loss"] = Answer.float_value("overall_loss", self.overall_loss, label="Overall loss")
|
|
77
80
|
|
|
78
|
-
def
|
|
79
|
-
"""
|
|
81
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Element, List[Answer]]:
|
|
82
|
+
"""Build question body and collect answers."""
|
|
80
83
|
body = ContentAST.Section()
|
|
84
|
+
answers = []
|
|
81
85
|
|
|
82
86
|
# Question description
|
|
83
87
|
body.add_element(ContentAST.Paragraph([
|
|
@@ -85,15 +89,25 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
85
89
|
f"and the overall {self._get_loss_function_short_name()}."
|
|
86
90
|
]))
|
|
87
91
|
|
|
88
|
-
# Data table
|
|
92
|
+
# Data table (contains individual loss answers)
|
|
89
93
|
body.add_element(self._create_data_table())
|
|
90
94
|
|
|
95
|
+
# Collect individual loss answers
|
|
96
|
+
for i in range(self.num_samples):
|
|
97
|
+
answers.append(self.answers[f"loss_{i}"])
|
|
98
|
+
|
|
91
99
|
# Overall loss question
|
|
92
100
|
body.add_element(ContentAST.Paragraph([
|
|
93
101
|
f"Overall {self._get_loss_function_short_name()}: "
|
|
94
102
|
]))
|
|
95
|
-
|
|
103
|
+
answers.append(self.answers["overall_loss"])
|
|
104
|
+
body.add_element(self.answers["overall_loss"])
|
|
105
|
+
|
|
106
|
+
return body, answers
|
|
96
107
|
|
|
108
|
+
def get_body(self, **kwargs) -> ContentAST.Element:
|
|
109
|
+
"""Build question body (backward compatible interface)."""
|
|
110
|
+
body, _ = self._get_body(**kwargs)
|
|
97
111
|
return body
|
|
98
112
|
|
|
99
113
|
@abc.abstractmethod
|
|
@@ -101,8 +115,8 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
101
115
|
"""Create the data table with answer fields."""
|
|
102
116
|
pass
|
|
103
117
|
|
|
104
|
-
def
|
|
105
|
-
"""
|
|
118
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Element, List[Answer]]:
|
|
119
|
+
"""Build question explanation."""
|
|
106
120
|
explanation = ContentAST.Section()
|
|
107
121
|
|
|
108
122
|
explanation.add_element(ContentAST.Paragraph([
|
|
@@ -121,6 +135,11 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
|
|
|
121
135
|
# Overall loss calculation
|
|
122
136
|
explanation.add_element(self._create_overall_loss_explanation())
|
|
123
137
|
|
|
138
|
+
return explanation, []
|
|
139
|
+
|
|
140
|
+
def get_explanation(self, **kwargs) -> ContentAST.Element:
|
|
141
|
+
"""Build question explanation (backward compatible interface)."""
|
|
142
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
124
143
|
return explanation
|
|
125
144
|
|
|
126
145
|
@abc.abstractmethod
|
|
@@ -36,15 +36,22 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
36
36
|
return [[f"{prefix}{matrix[i][j]}" for j in range(len(matrix[0]))] for i in range(len(matrix))]
|
|
37
37
|
|
|
38
38
|
def _create_answer_table(self, rows, cols, answers_dict, answer_prefix="answer"):
|
|
39
|
-
"""Create a table with answer blanks for matrix results.
|
|
39
|
+
"""Create a table with answer blanks for matrix results.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Tuple of (table, answers_list)
|
|
43
|
+
"""
|
|
40
44
|
table_data = []
|
|
45
|
+
answers = []
|
|
41
46
|
for i in range(rows):
|
|
42
47
|
row = []
|
|
43
48
|
for j in range(cols):
|
|
44
49
|
answer_key = f"{answer_prefix}_{i}_{j}"
|
|
45
|
-
|
|
50
|
+
ans = answers_dict[answer_key]
|
|
51
|
+
row.append(ans)
|
|
52
|
+
answers.append(ans)
|
|
46
53
|
table_data.append(row)
|
|
47
|
-
return ContentAST.Table(data=table_data, padding=True)
|
|
54
|
+
return ContentAST.Table(data=table_data, padding=True), answers
|
|
48
55
|
|
|
49
56
|
# Implement MathOperationQuestion abstract methods
|
|
50
57
|
|
|
@@ -64,23 +71,23 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
64
71
|
return f"{operand_a_latex} {self.get_operator()} {operand_b_latex}"
|
|
65
72
|
|
|
66
73
|
def _add_single_question_answers(self, body):
|
|
67
|
-
"""Add Canvas-only answer fields for single questions.
|
|
74
|
+
"""Add Canvas-only answer fields for single questions.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of Answer objects that were added to the body.
|
|
78
|
+
"""
|
|
79
|
+
answers = []
|
|
80
|
+
|
|
68
81
|
# For matrices, we typically show result dimensions and answer table
|
|
69
82
|
if hasattr(self, 'result_rows') and hasattr(self, 'result_cols'):
|
|
70
83
|
# Matrix multiplication case with dimension answers
|
|
71
84
|
if hasattr(self, 'answers') and "result_rows" in self.answers:
|
|
85
|
+
rows_ans = self.answers["result_rows"]
|
|
86
|
+
cols_ans = self.answers["result_cols"]
|
|
87
|
+
answers.extend([rows_ans, cols_ans])
|
|
72
88
|
body.add_element(
|
|
73
89
|
ContentAST.OnlyHtml([
|
|
74
|
-
ContentAST.AnswerBlock([
|
|
75
|
-
ContentAST.Answer(
|
|
76
|
-
answer=self.answers["result_rows"],
|
|
77
|
-
label="Number of rows in result"
|
|
78
|
-
),
|
|
79
|
-
ContentAST.Answer(
|
|
80
|
-
answer=self.answers["result_cols"],
|
|
81
|
-
label="Number of columns in result"
|
|
82
|
-
)
|
|
83
|
-
])
|
|
90
|
+
ContentAST.AnswerBlock([rows_ans, cols_ans])
|
|
84
91
|
])
|
|
85
92
|
)
|
|
86
93
|
|
|
@@ -88,21 +95,27 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
88
95
|
if hasattr(self, 'result') and self.result:
|
|
89
96
|
rows = len(self.result)
|
|
90
97
|
cols = len(self.result[0])
|
|
98
|
+
table, table_answers = self._create_answer_table(rows, cols, self.answers)
|
|
99
|
+
answers.extend(table_answers)
|
|
91
100
|
body.add_element(
|
|
92
101
|
ContentAST.OnlyHtml([
|
|
93
102
|
ContentAST.Paragraph(["Result matrix:"]),
|
|
94
|
-
|
|
103
|
+
table
|
|
95
104
|
])
|
|
96
105
|
)
|
|
97
106
|
elif hasattr(self, 'max_dim'):
|
|
98
107
|
# Matrix multiplication with max dimensions
|
|
108
|
+
table, table_answers = self._create_answer_table(self.max_dim, self.max_dim, self.answers)
|
|
109
|
+
answers.extend(table_answers)
|
|
99
110
|
body.add_element(
|
|
100
111
|
ContentAST.OnlyHtml([
|
|
101
112
|
ContentAST.Paragraph(["Result matrix (use '-' if cell doesn't exist):"]),
|
|
102
|
-
|
|
113
|
+
table
|
|
103
114
|
])
|
|
104
115
|
)
|
|
105
116
|
|
|
117
|
+
return answers
|
|
118
|
+
|
|
106
119
|
# Abstract methods that subclasses must implement
|
|
107
120
|
@abc.abstractmethod
|
|
108
121
|
def get_operator(self):
|
|
@@ -488,8 +501,10 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
488
501
|
# For single questions, use the old answer format
|
|
489
502
|
# Dimension answers
|
|
490
503
|
if result is not None:
|
|
491
|
-
self.answers["result_rows"] = Answer.integer("result_rows", self.result_rows
|
|
492
|
-
|
|
504
|
+
self.answers["result_rows"] = Answer.integer("result_rows", self.result_rows,
|
|
505
|
+
label="Number of rows in result")
|
|
506
|
+
self.answers["result_cols"] = Answer.integer("result_cols", self.result_cols,
|
|
507
|
+
label="Number of columns in result")
|
|
493
508
|
|
|
494
509
|
# Matrix element answers
|
|
495
510
|
for i in range(self.max_dim):
|
|
@@ -501,8 +516,10 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
501
516
|
self.answers[answer_key] = Answer.string(answer_key, "-")
|
|
502
517
|
else:
|
|
503
518
|
# Multiplication not possible
|
|
504
|
-
self.answers["result_rows"] = Answer.string("result_rows", "-"
|
|
505
|
-
|
|
519
|
+
self.answers["result_rows"] = Answer.string("result_rows", "-",
|
|
520
|
+
label="Number of rows in result")
|
|
521
|
+
self.answers["result_cols"] = Answer.string("result_cols", "-",
|
|
522
|
+
label="Number of columns in result")
|
|
506
523
|
|
|
507
524
|
# All matrix elements are "-"
|
|
508
525
|
for i in range(self.max_dim):
|
|
@@ -523,32 +540,36 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
523
540
|
self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
|
|
524
541
|
|
|
525
542
|
def _add_single_question_answers(self, body):
|
|
526
|
-
"""Add Canvas-only answer fields for MatrixMultiplication with dash instruction.
|
|
543
|
+
"""Add Canvas-only answer fields for MatrixMultiplication with dash instruction.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
List of Answer objects that were added to the body.
|
|
547
|
+
"""
|
|
548
|
+
answers = []
|
|
549
|
+
|
|
527
550
|
# Dimension answers for matrix multiplication
|
|
528
551
|
if hasattr(self, 'answers') and "result_rows" in self.answers:
|
|
552
|
+
rows_ans = self.answers["result_rows"]
|
|
553
|
+
cols_ans = self.answers["result_cols"]
|
|
554
|
+
answers.extend([rows_ans, cols_ans])
|
|
529
555
|
body.add_element(
|
|
530
556
|
ContentAST.OnlyHtml([
|
|
531
|
-
ContentAST.AnswerBlock([
|
|
532
|
-
ContentAST.Answer(
|
|
533
|
-
answer=self.answers["result_rows"],
|
|
534
|
-
label="Number of rows in result"
|
|
535
|
-
),
|
|
536
|
-
ContentAST.Answer(
|
|
537
|
-
answer=self.answers["result_cols"],
|
|
538
|
-
label="Number of columns in result"
|
|
539
|
-
)
|
|
540
|
-
])
|
|
557
|
+
ContentAST.AnswerBlock([rows_ans, cols_ans])
|
|
541
558
|
])
|
|
542
559
|
)
|
|
543
560
|
|
|
544
561
|
# Matrix result table with dash instruction
|
|
562
|
+
table, table_answers = self._create_answer_table(self.max_dim, self.max_dim, self.answers)
|
|
563
|
+
answers.extend(table_answers)
|
|
545
564
|
body.add_element(
|
|
546
565
|
ContentAST.OnlyHtml([
|
|
547
566
|
ContentAST.Paragraph(["Result matrix (use '-' if cell doesn't exist):"]),
|
|
548
|
-
|
|
567
|
+
table
|
|
549
568
|
])
|
|
550
569
|
)
|
|
551
570
|
|
|
571
|
+
return answers
|
|
572
|
+
|
|
552
573
|
def refresh(self, *args, **kwargs):
|
|
553
574
|
"""Override refresh to handle matrix attributes."""
|
|
554
575
|
super().refresh(*args, **kwargs)
|