QuizGenerator 0.4.4__py3-none-any.whl → 0.5.1__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 +952 -82
- QuizGenerator/generate.py +45 -9
- 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 +554 -64
- 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 +117 -51
- QuizGenerator/question.py +110 -15
- QuizGenerator/quiz.py +81 -24
- QuizGenerator/regenerate.py +98 -29
- {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/METADATA +1 -1
- {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/RECORD +29 -31
- QuizGenerator/README.md +0 -5
- QuizGenerator/logging.yaml +0 -55
- {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,10 +22,16 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
22
22
|
return [self.rng.randint(min_val, max_val) for _ in range(dimension)]
|
|
23
23
|
|
|
24
24
|
def _format_vector(self, vector):
|
|
25
|
-
"""
|
|
26
|
-
|
|
25
|
+
"""Return a ContentAST.Matrix element for the vector (format-independent).
|
|
26
|
+
|
|
27
|
+
The Matrix element will render appropriately for each output format:
|
|
28
|
+
- HTML: LaTeX bmatrix (for MathJax)
|
|
29
|
+
- Typst: mat() with square bracket delimiter
|
|
30
|
+
- LaTeX: bmatrix environment
|
|
31
|
+
"""
|
|
32
|
+
# Convert to column matrix format: [[v1], [v2], [v3]]
|
|
27
33
|
matrix_data = [[v] for v in vector]
|
|
28
|
-
return ContentAST.Matrix
|
|
34
|
+
return ContentAST.Matrix(data=matrix_data, bracket_type="b")
|
|
29
35
|
|
|
30
36
|
def _format_vector_inline(self, vector):
|
|
31
37
|
"""Format vector for inline display."""
|
|
@@ -81,23 +87,6 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
81
87
|
subparts.append((vector_a_latex, self.get_operator(), vector_b_latex))
|
|
82
88
|
return subparts
|
|
83
89
|
|
|
84
|
-
def _add_single_question_answers(self, body):
|
|
85
|
-
"""Add Canvas-only answer fields for single questions."""
|
|
86
|
-
# Check if it's a scalar result (like dot product)
|
|
87
|
-
if hasattr(self, 'answers') and len(self.answers) == 1:
|
|
88
|
-
# Single scalar answer
|
|
89
|
-
answer_key = list(self.answers.keys())[0]
|
|
90
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Answer(answer=self.answers[answer_key])]))
|
|
91
|
-
else:
|
|
92
|
-
# Vector results (like addition) - show table
|
|
93
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
94
|
-
table_data = []
|
|
95
|
-
for i in range(self.dimension):
|
|
96
|
-
if f"result_{i}" in self.answers:
|
|
97
|
-
table_data.append([ContentAST.Answer(answer=self.answers[f"result_{i}"])])
|
|
98
|
-
if table_data:
|
|
99
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
100
|
-
|
|
101
90
|
# Abstract methods that subclasses must still implement
|
|
102
91
|
@abc.abstractmethod
|
|
103
92
|
def get_operator(self):
|
|
@@ -133,10 +122,61 @@ class VectorAddition(VectorMathQuestion):
|
|
|
133
122
|
self.answers[f"subpart_{letter}_{j}"] = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
134
123
|
|
|
135
124
|
def create_single_answers(self, result):
|
|
125
|
+
# Backward compatibility - still populate dict for old pattern
|
|
136
126
|
for i in range(len(result)):
|
|
137
127
|
self.answers[f"result_{i}"] = Answer.integer(f"result_{i}", result[i])
|
|
138
128
|
|
|
139
|
-
def
|
|
129
|
+
def get_body(self):
|
|
130
|
+
"""Override parent get_body() to use our custom formatting."""
|
|
131
|
+
body, _ = self._get_body()
|
|
132
|
+
return body
|
|
133
|
+
|
|
134
|
+
def _get_body(self):
|
|
135
|
+
"""Build question body and collect answers (new pattern)."""
|
|
136
|
+
from typing import Tuple, List
|
|
137
|
+
|
|
138
|
+
body = ContentAST.Section()
|
|
139
|
+
answers = []
|
|
140
|
+
|
|
141
|
+
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
142
|
+
|
|
143
|
+
if self.is_multipart():
|
|
144
|
+
# Use multipart formatting with repeated problem parts
|
|
145
|
+
subpart_data = self.generate_subquestion_data()
|
|
146
|
+
repeated_part = self.create_repeated_problem_part(subpart_data)
|
|
147
|
+
body.add_element(repeated_part)
|
|
148
|
+
|
|
149
|
+
# Collect all subpart answers
|
|
150
|
+
for i, data in enumerate(self.subquestion_data):
|
|
151
|
+
letter = chr(ord('a') + i)
|
|
152
|
+
result = data['result']
|
|
153
|
+
for j in range(len(result)):
|
|
154
|
+
ans = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
155
|
+
answers.append(ans)
|
|
156
|
+
else:
|
|
157
|
+
# Single equation display using MathExpression for format-independent rendering
|
|
158
|
+
vector_a_elem = self._format_vector(self.vector_a)
|
|
159
|
+
vector_b_elem = self._format_vector(self.vector_b)
|
|
160
|
+
body.add_element(ContentAST.MathExpression([
|
|
161
|
+
vector_a_elem,
|
|
162
|
+
" + ",
|
|
163
|
+
vector_b_elem,
|
|
164
|
+
" = "
|
|
165
|
+
]))
|
|
166
|
+
|
|
167
|
+
# Canvas-only answer fields (hidden from PDF)
|
|
168
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
169
|
+
table_data = []
|
|
170
|
+
for i in range(self.dimension):
|
|
171
|
+
ans = Answer.integer(f"result_{i}", self.result[i])
|
|
172
|
+
answers.append(ans)
|
|
173
|
+
table_data.append([ans])
|
|
174
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
175
|
+
|
|
176
|
+
return body, answers
|
|
177
|
+
|
|
178
|
+
def _get_explanation(self):
|
|
179
|
+
"""Build question explanation (new pattern)."""
|
|
140
180
|
explanation = ContentAST.Section()
|
|
141
181
|
|
|
142
182
|
explanation.add_element(ContentAST.Paragraph(["To add vectors, we add corresponding components:"]))
|
|
@@ -149,43 +189,49 @@ class VectorAddition(VectorMathQuestion):
|
|
|
149
189
|
vector_b = data['vector_b']
|
|
150
190
|
result = data['result']
|
|
151
191
|
|
|
152
|
-
#
|
|
153
|
-
vector_a_str =
|
|
154
|
-
vector_b_str =
|
|
155
|
-
|
|
156
|
-
|
|
192
|
+
# Format vectors using Typst syntax
|
|
193
|
+
vector_a_str = self._format_vector(vector_a)
|
|
194
|
+
vector_b_str = self._format_vector(vector_b)
|
|
195
|
+
result_str = self._format_vector(result)
|
|
196
|
+
|
|
197
|
+
# Build addition step-by-step
|
|
198
|
+
addition_elements = "; ".join([f"{vector_a[j]}+{vector_b[j]}" for j in range(self.dimension)])
|
|
199
|
+
addition_str = f"mat({addition_elements})"
|
|
157
200
|
|
|
158
201
|
# Add explanation for this subpart
|
|
159
202
|
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
160
203
|
explanation.add_element(
|
|
161
204
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
162
|
-
lhs="
|
|
205
|
+
lhs="arrow(a) + arrow(b)", # Typst uses arrow() for vectors
|
|
163
206
|
rhs=[
|
|
164
|
-
f"
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
f"{vector_a_str} + {vector_b_str}",
|
|
208
|
+
addition_str,
|
|
209
|
+
result_str
|
|
167
210
|
]
|
|
168
211
|
)
|
|
169
212
|
)
|
|
170
213
|
else:
|
|
171
|
-
# Single part explanation
|
|
172
|
-
vector_a_str =
|
|
173
|
-
vector_b_str =
|
|
174
|
-
|
|
175
|
-
|
|
214
|
+
# Single part explanation - use Typst syntax
|
|
215
|
+
vector_a_str = self._format_vector(self.vector_a)
|
|
216
|
+
vector_b_str = self._format_vector(self.vector_b)
|
|
217
|
+
result_str = self._format_vector(self.result)
|
|
218
|
+
|
|
219
|
+
# Build addition step-by-step
|
|
220
|
+
addition_elements = "; ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
|
|
221
|
+
addition_str = f"mat({addition_elements})"
|
|
176
222
|
|
|
177
223
|
explanation.add_element(
|
|
178
224
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
179
|
-
lhs="
|
|
225
|
+
lhs="arrow(a) + arrow(b)",
|
|
180
226
|
rhs=[
|
|
181
|
-
f"
|
|
182
|
-
|
|
183
|
-
|
|
227
|
+
f"{vector_a_str} + {vector_b_str}",
|
|
228
|
+
addition_str,
|
|
229
|
+
result_str
|
|
184
230
|
]
|
|
185
231
|
)
|
|
186
232
|
)
|
|
187
233
|
|
|
188
|
-
return explanation
|
|
234
|
+
return explanation, [] # Explanations don't have answers
|
|
189
235
|
|
|
190
236
|
|
|
191
237
|
@QuestionRegistry.register()
|
|
@@ -269,15 +315,24 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
269
315
|
"""Override to handle scalar multiplication format."""
|
|
270
316
|
subparts = []
|
|
271
317
|
for data in self.subquestion_data:
|
|
272
|
-
|
|
273
|
-
# For scalar multiplication, we show scalar * vector as a single string
|
|
274
|
-
# Use the scalar from this specific subquestion's data
|
|
318
|
+
vector_elem = self._format_vector(data['vector_a'])
|
|
275
319
|
scalar = data['scalar']
|
|
276
|
-
|
|
320
|
+
# Return MathExpression for format-independent rendering
|
|
321
|
+
subparts.append(ContentAST.MathExpression([
|
|
322
|
+
f"{scalar} \\cdot ",
|
|
323
|
+
vector_elem
|
|
324
|
+
], inline=True))
|
|
277
325
|
return subparts
|
|
278
326
|
|
|
279
327
|
def get_body(self):
|
|
328
|
+
"""Override parent get_body() to use our custom formatting."""
|
|
329
|
+
body, _ = self._get_body()
|
|
330
|
+
return body
|
|
331
|
+
|
|
332
|
+
def _get_body(self):
|
|
333
|
+
"""Build question body and collect answers (new pattern)."""
|
|
280
334
|
body = ContentAST.Section()
|
|
335
|
+
answers = []
|
|
281
336
|
|
|
282
337
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
283
338
|
|
|
@@ -286,17 +341,36 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
286
341
|
subpart_data = self.generate_subquestion_data()
|
|
287
342
|
repeated_part = self.create_repeated_problem_part(subpart_data)
|
|
288
343
|
body.add_element(repeated_part)
|
|
344
|
+
|
|
345
|
+
# Collect all subpart answers
|
|
346
|
+
for i, data in enumerate(self.subquestion_data):
|
|
347
|
+
letter = chr(ord('a') + i)
|
|
348
|
+
result = data['result']
|
|
349
|
+
for j in range(len(result)):
|
|
350
|
+
ans = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
351
|
+
answers.append(ans)
|
|
289
352
|
else:
|
|
290
|
-
# Single equation display
|
|
291
|
-
|
|
292
|
-
body.add_element(ContentAST.
|
|
353
|
+
# Single equation display using MathExpression
|
|
354
|
+
vector_elem = self._format_vector(self.vector_a)
|
|
355
|
+
body.add_element(ContentAST.MathExpression([
|
|
356
|
+
f"{self.scalar} \\cdot ",
|
|
357
|
+
vector_elem,
|
|
358
|
+
" = "
|
|
359
|
+
]))
|
|
293
360
|
|
|
294
361
|
# Canvas-only answer fields (hidden from PDF)
|
|
295
|
-
|
|
362
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
363
|
+
table_data = []
|
|
364
|
+
for i in range(self.dimension):
|
|
365
|
+
ans = Answer.integer(f"result_{i}", self.result[i])
|
|
366
|
+
answers.append(ans)
|
|
367
|
+
table_data.append([ans])
|
|
368
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
296
369
|
|
|
297
|
-
return body
|
|
370
|
+
return body, answers
|
|
298
371
|
|
|
299
|
-
def
|
|
372
|
+
def _get_explanation(self):
|
|
373
|
+
"""Build question explanation (new pattern)."""
|
|
300
374
|
explanation = ContentAST.Section()
|
|
301
375
|
|
|
302
376
|
explanation.add_element(ContentAST.Paragraph(["To multiply a vector by a scalar, we multiply each component by the scalar:"]))
|
|
@@ -307,16 +381,13 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
307
381
|
letter = chr(ord('a') + i)
|
|
308
382
|
vector_a = data['vector_a']
|
|
309
383
|
result = data['result']
|
|
310
|
-
|
|
311
|
-
# Get the scalar for this specific subpart
|
|
312
384
|
scalar = data['scalar']
|
|
313
385
|
|
|
314
|
-
#
|
|
386
|
+
# Use LaTeX syntax (will be converted by Equation._latex_to_typst for Typst output)
|
|
315
387
|
vector_str = r" \\ ".join([str(v) for v in vector_a])
|
|
316
388
|
multiplication_str = r" \\ ".join([f"{scalar} \\cdot {v}" for v in vector_a])
|
|
317
389
|
result_str = r" \\ ".join([str(v) for v in result])
|
|
318
390
|
|
|
319
|
-
# Add explanation for this subpart
|
|
320
391
|
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
321
392
|
explanation.add_element(
|
|
322
393
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
@@ -329,7 +400,6 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
329
400
|
)
|
|
330
401
|
)
|
|
331
402
|
else:
|
|
332
|
-
# Single part explanation - use the correct attributes
|
|
333
403
|
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
334
404
|
multiplication_str = r" \\ ".join([f"{self.scalar} \\cdot {v}" for v in self.vector_a])
|
|
335
405
|
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
@@ -345,7 +415,7 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
345
415
|
)
|
|
346
416
|
)
|
|
347
417
|
|
|
348
|
-
return explanation
|
|
418
|
+
return explanation, [] # Explanations don't have answers
|
|
349
419
|
|
|
350
420
|
|
|
351
421
|
@QuestionRegistry.register()
|
|
@@ -369,26 +439,66 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
369
439
|
"dot_product": Answer.integer("dot_product", result)
|
|
370
440
|
}
|
|
371
441
|
|
|
372
|
-
def
|
|
442
|
+
def get_body(self):
|
|
443
|
+
"""Override parent get_body() to use our custom formatting."""
|
|
444
|
+
body, _ = self._get_body()
|
|
445
|
+
return body
|
|
446
|
+
|
|
447
|
+
def _get_body(self):
|
|
448
|
+
"""Build question body and collect answers (new pattern)."""
|
|
449
|
+
body = ContentAST.Section()
|
|
450
|
+
answers = []
|
|
451
|
+
|
|
452
|
+
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
453
|
+
|
|
454
|
+
if self.is_multipart():
|
|
455
|
+
# Use multipart formatting with repeated problem parts
|
|
456
|
+
subpart_data = self.generate_subquestion_data()
|
|
457
|
+
repeated_part = self.create_repeated_problem_part(subpart_data)
|
|
458
|
+
body.add_element(repeated_part)
|
|
459
|
+
|
|
460
|
+
# Collect all subpart answers (scalar results)
|
|
461
|
+
for i, data in enumerate(self.subquestion_data):
|
|
462
|
+
letter = chr(ord('a') + i)
|
|
463
|
+
result = data['result']
|
|
464
|
+
ans = Answer.integer(f"subpart_{letter}", result)
|
|
465
|
+
answers.append(ans)
|
|
466
|
+
else:
|
|
467
|
+
# Single equation display using MathExpression
|
|
468
|
+
vector_a_elem = self._format_vector(self.vector_a)
|
|
469
|
+
vector_b_elem = self._format_vector(self.vector_b)
|
|
470
|
+
body.add_element(ContentAST.MathExpression([
|
|
471
|
+
vector_a_elem,
|
|
472
|
+
" \\cdot ",
|
|
473
|
+
vector_b_elem,
|
|
474
|
+
" = "
|
|
475
|
+
]))
|
|
476
|
+
|
|
477
|
+
# Canvas-only answer field (single scalar result)
|
|
478
|
+
ans = Answer.integer("dot_product", self.result)
|
|
479
|
+
answers.append(ans)
|
|
480
|
+
body.add_element(ContentAST.OnlyHtml([ans]))
|
|
481
|
+
|
|
482
|
+
return body, answers
|
|
483
|
+
|
|
484
|
+
def _get_explanation(self):
|
|
485
|
+
"""Build question explanation (new pattern)."""
|
|
373
486
|
explanation = ContentAST.Section()
|
|
374
487
|
|
|
375
488
|
explanation.add_element(ContentAST.Paragraph(["The dot product is calculated by multiplying corresponding components and summing the results:"]))
|
|
376
489
|
|
|
377
490
|
if self.is_multipart():
|
|
378
|
-
# Handle multipart explanations
|
|
379
491
|
for i, data in enumerate(self.subquestion_data):
|
|
380
492
|
letter = chr(ord('a') + i)
|
|
381
493
|
vector_a = data['vector_a']
|
|
382
494
|
vector_b = data['vector_b']
|
|
383
495
|
result = data['result']
|
|
384
496
|
|
|
385
|
-
# Create LaTeX strings for multiline equation
|
|
386
497
|
vector_a_str = r" \\ ".join([str(v) for v in vector_a])
|
|
387
498
|
vector_b_str = r" \\ ".join([str(v) for v in vector_b])
|
|
388
499
|
products_str = " + ".join([f"({vector_a[j]} \\cdot {vector_b[j]})" for j in range(self.dimension)])
|
|
389
500
|
calculation_str = " + ".join([str(vector_a[j] * vector_b[j]) for j in range(self.dimension)])
|
|
390
501
|
|
|
391
|
-
# Add explanation for this subpart
|
|
392
502
|
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
393
503
|
explanation.add_element(
|
|
394
504
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
@@ -402,7 +512,6 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
402
512
|
)
|
|
403
513
|
)
|
|
404
514
|
else:
|
|
405
|
-
# Single part explanation (original behavior)
|
|
406
515
|
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
407
516
|
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
408
517
|
products_str = " + ".join([f"({self.vector_a[i]} \\cdot {self.vector_b[i]})" for i in range(self.dimension)])
|
|
@@ -420,7 +529,7 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
420
529
|
)
|
|
421
530
|
)
|
|
422
531
|
|
|
423
|
-
return explanation
|
|
532
|
+
return explanation, [] # Explanations don't have answers
|
|
424
533
|
|
|
425
534
|
|
|
426
535
|
@QuestionRegistry.register()
|
|
@@ -439,25 +548,43 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
439
548
|
return math.sqrt(magnitude_squared)
|
|
440
549
|
|
|
441
550
|
def create_subquestion_answers(self, subpart_index, result):
|
|
551
|
+
# Backward compatibility
|
|
442
552
|
letter = chr(ord('a') + subpart_index)
|
|
443
553
|
self.answers[f"subpart_{letter}"] = Answer.auto_float(f"subpart_{letter}", result)
|
|
444
554
|
|
|
445
555
|
def create_single_answers(self, result):
|
|
556
|
+
# Backward compatibility
|
|
446
557
|
self.answers = {
|
|
447
558
|
"magnitude": Answer.auto_float("magnitude", result)
|
|
448
559
|
}
|
|
449
560
|
|
|
450
561
|
def generate_subquestion_data(self):
|
|
451
|
-
"""Override to handle magnitude format ||vector||.
|
|
562
|
+
"""Override to handle magnitude format ||vector||.
|
|
563
|
+
|
|
564
|
+
Returns MathExpression elements for format-independent rendering.
|
|
565
|
+
"""
|
|
452
566
|
subparts = []
|
|
453
567
|
for data in self.subquestion_data:
|
|
454
|
-
|
|
455
|
-
#
|
|
456
|
-
subparts.append(
|
|
568
|
+
vector_elem = self._format_vector(data['vector_a'])
|
|
569
|
+
# Create MathExpression for ||vector||
|
|
570
|
+
subparts.append(ContentAST.MathExpression([
|
|
571
|
+
"||",
|
|
572
|
+
vector_elem,
|
|
573
|
+
"||"
|
|
574
|
+
], inline=True))
|
|
457
575
|
return subparts
|
|
458
576
|
|
|
459
577
|
def get_body(self):
|
|
578
|
+
"""Override parent get_body() to use our custom formatting."""
|
|
579
|
+
body, _ = self._get_body()
|
|
580
|
+
return body
|
|
581
|
+
|
|
582
|
+
def _get_body(self):
|
|
583
|
+
"""Build question body and collect answers (new pattern)."""
|
|
584
|
+
from typing import Tuple, List
|
|
585
|
+
|
|
460
586
|
body = ContentAST.Section()
|
|
587
|
+
answers = []
|
|
461
588
|
|
|
462
589
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
463
590
|
|
|
@@ -466,21 +593,34 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
466
593
|
subpart_data = self.generate_subquestion_data()
|
|
467
594
|
repeated_part = self.create_repeated_problem_part(subpart_data)
|
|
468
595
|
body.add_element(repeated_part)
|
|
596
|
+
|
|
597
|
+
# Collect all subpart answers
|
|
598
|
+
for i, data in enumerate(self.subquestion_data):
|
|
599
|
+
letter = chr(ord('a') + i)
|
|
600
|
+
result = data['result']
|
|
601
|
+
ans = Answer.auto_float(f"subpart_{letter}", result)
|
|
602
|
+
answers.append(ans)
|
|
469
603
|
else:
|
|
470
|
-
# Single equation display
|
|
471
|
-
|
|
472
|
-
body.add_element(ContentAST.
|
|
604
|
+
# Single equation display using MathExpression for format-independent rendering
|
|
605
|
+
vector_elem = self._format_vector(self.vector_a)
|
|
606
|
+
body.add_element(ContentAST.MathExpression([
|
|
607
|
+
"||",
|
|
608
|
+
vector_elem,
|
|
609
|
+
"|| = "
|
|
610
|
+
]))
|
|
473
611
|
|
|
474
612
|
# Canvas-only answer field (hidden from PDF)
|
|
475
|
-
self.
|
|
613
|
+
ans = Answer.auto_float("magnitude", self.result)
|
|
614
|
+
answers.append(ans)
|
|
615
|
+
body.add_element(ContentAST.OnlyHtml([ans]))
|
|
476
616
|
|
|
477
|
-
return body
|
|
617
|
+
return body, answers
|
|
478
618
|
|
|
479
|
-
def
|
|
619
|
+
def _get_explanation(self):
|
|
480
620
|
explanation = ContentAST.Section()
|
|
481
621
|
|
|
482
622
|
explanation.add_element(ContentAST.Paragraph(["The magnitude of a vector is calculated using the formula:"]))
|
|
483
|
-
explanation.add_element(ContentAST.Equation("
|
|
623
|
+
explanation.add_element(ContentAST.Equation("||arrow(v)|| = sqrt(v_1^2 + v_2^2 + ... + v_n^2)", inline=False))
|
|
484
624
|
|
|
485
625
|
if self.is_multipart():
|
|
486
626
|
# Handle multipart explanations
|
|
@@ -489,8 +629,8 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
489
629
|
vector_a = data['vector_a']
|
|
490
630
|
result = data['result']
|
|
491
631
|
|
|
492
|
-
#
|
|
493
|
-
vector_str =
|
|
632
|
+
# Format vectors using Typst syntax
|
|
633
|
+
vector_str = self._format_vector(vector_a)
|
|
494
634
|
squares_str = " + ".join([f"{v}^2" for v in vector_a])
|
|
495
635
|
calculation_str = " + ".join([str(v**2) for v in vector_a])
|
|
496
636
|
sum_of_squares = sum(component ** 2 for component in vector_a)
|
|
@@ -500,19 +640,19 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
500
640
|
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
501
641
|
explanation.add_element(
|
|
502
642
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
503
|
-
lhs="
|
|
643
|
+
lhs="||arrow(v)||",
|
|
504
644
|
rhs=[
|
|
505
|
-
f"
|
|
506
|
-
f"
|
|
507
|
-
f"
|
|
508
|
-
f"
|
|
645
|
+
f"||{vector_str}||",
|
|
646
|
+
f"sqrt({squares_str})",
|
|
647
|
+
f"sqrt({calculation_str})",
|
|
648
|
+
f"sqrt({sum_of_squares})",
|
|
509
649
|
result_formatted
|
|
510
650
|
]
|
|
511
651
|
)
|
|
512
652
|
)
|
|
513
653
|
else:
|
|
514
|
-
# Single part explanation - use
|
|
515
|
-
vector_str =
|
|
654
|
+
# Single part explanation - use Typst syntax
|
|
655
|
+
vector_str = self._format_vector(self.vector_a)
|
|
516
656
|
squares_str = " + ".join([f"{v}^2" for v in self.vector_a])
|
|
517
657
|
calculation_str = " + ".join([str(v**2) for v in self.vector_a])
|
|
518
658
|
sum_of_squares = sum(component ** 2 for component in self.vector_a)
|
|
@@ -520,15 +660,15 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
520
660
|
|
|
521
661
|
explanation.add_element(
|
|
522
662
|
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
523
|
-
lhs="
|
|
663
|
+
lhs="||arrow(v)||",
|
|
524
664
|
rhs=[
|
|
525
|
-
f"
|
|
526
|
-
f"
|
|
527
|
-
f"
|
|
528
|
-
f"
|
|
665
|
+
f"||{vector_str}||",
|
|
666
|
+
f"sqrt({squares_str})",
|
|
667
|
+
f"sqrt({calculation_str})",
|
|
668
|
+
f"sqrt({sum_of_squares})",
|
|
529
669
|
result_formatted
|
|
530
670
|
]
|
|
531
671
|
)
|
|
532
672
|
)
|
|
533
673
|
|
|
534
|
-
return explanation
|
|
674
|
+
return explanation, [] # Explanations don't have answers
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import math
|
|
4
4
|
import keras
|
|
5
5
|
import numpy as np
|
|
6
|
+
from typing import List, Tuple
|
|
6
7
|
|
|
7
8
|
from QuizGenerator.misc import MatrixAnswer
|
|
8
9
|
from QuizGenerator.question import Question, QuestionRegistry, Answer
|
|
@@ -60,15 +61,17 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
60
61
|
|
|
61
62
|
## Answers:
|
|
62
63
|
# Q, K, V, output, weights
|
|
63
|
-
|
|
64
|
-
self.answers["weights"] = MatrixAnswer("weights", self.
|
|
65
|
-
self.answers["output"] = MatrixAnswer("output", self.output)
|
|
64
|
+
|
|
65
|
+
self.answers["weights"] = MatrixAnswer("weights", self.weights, label="Weights")
|
|
66
|
+
self.answers["output"] = MatrixAnswer("output", self.output, label="Output")
|
|
66
67
|
|
|
67
68
|
return True
|
|
68
69
|
|
|
69
|
-
def
|
|
70
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
71
|
+
"""Build question body and collect answers."""
|
|
70
72
|
body = ContentAST.Section()
|
|
71
|
-
|
|
73
|
+
answers = []
|
|
74
|
+
|
|
72
75
|
body.add_element(
|
|
73
76
|
ContentAST.Text("Given the below information about a self attention layer, please calculate the output sequence.")
|
|
74
77
|
)
|
|
@@ -81,17 +84,25 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
81
84
|
}
|
|
82
85
|
)
|
|
83
86
|
)
|
|
84
|
-
|
|
87
|
+
|
|
88
|
+
answers.append(self.answers["weights"])
|
|
89
|
+
answers.append(self.answers["output"])
|
|
85
90
|
body.add_elements([
|
|
86
91
|
ContentAST.LineBreak(),
|
|
87
|
-
self.answers["weights"]
|
|
92
|
+
self.answers["weights"],
|
|
88
93
|
ContentAST.LineBreak(),
|
|
89
|
-
self.answers["output"]
|
|
94
|
+
self.answers["output"],
|
|
90
95
|
])
|
|
91
|
-
|
|
96
|
+
|
|
97
|
+
return body, answers
|
|
98
|
+
|
|
99
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
100
|
+
"""Build question body (backward compatible interface)."""
|
|
101
|
+
body, _ = self._get_body(**kwargs)
|
|
92
102
|
return body
|
|
93
103
|
|
|
94
|
-
def
|
|
104
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
105
|
+
"""Build question explanation."""
|
|
95
106
|
explanation = ContentAST.Section()
|
|
96
107
|
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
97
108
|
|
|
@@ -188,5 +199,10 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
|
|
|
188
199
|
)
|
|
189
200
|
explanation.add_element(ContentAST.Matrix(np.round(self.output, digits)))
|
|
190
201
|
|
|
202
|
+
return explanation, []
|
|
203
|
+
|
|
204
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
205
|
+
"""Build question explanation (backward compatible interface)."""
|
|
206
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
191
207
|
return explanation
|
|
192
208
|
|