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.
Files changed (31) hide show
  1. QuizGenerator/contentast.py +952 -82
  2. QuizGenerator/generate.py +45 -9
  3. QuizGenerator/misc.py +4 -554
  4. QuizGenerator/mixins.py +47 -25
  5. QuizGenerator/premade_questions/cst334/languages.py +139 -125
  6. QuizGenerator/premade_questions/cst334/math_questions.py +78 -66
  7. QuizGenerator/premade_questions/cst334/memory_questions.py +258 -144
  8. QuizGenerator/premade_questions/cst334/persistence_questions.py +71 -33
  9. QuizGenerator/premade_questions/cst334/process.py +554 -64
  10. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +32 -6
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +59 -34
  12. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +27 -8
  13. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +53 -32
  14. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +228 -88
  15. QuizGenerator/premade_questions/cst463/models/attention.py +26 -10
  16. QuizGenerator/premade_questions/cst463/models/cnns.py +32 -19
  17. QuizGenerator/premade_questions/cst463/models/rnns.py +25 -12
  18. QuizGenerator/premade_questions/cst463/models/text.py +26 -11
  19. QuizGenerator/premade_questions/cst463/models/weight_counting.py +36 -22
  20. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +89 -109
  21. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +117 -51
  22. QuizGenerator/question.py +110 -15
  23. QuizGenerator/quiz.py +81 -24
  24. QuizGenerator/regenerate.py +98 -29
  25. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/METADATA +1 -1
  26. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/RECORD +29 -31
  27. QuizGenerator/README.md +0 -5
  28. QuizGenerator/logging.yaml +0 -55
  29. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/WHEEL +0 -0
  30. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.1.dist-info}/entry_points.txt +0 -0
  31. {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
- """Format vector for display as column vector using ContentAST.Matrix."""
26
- # Convert to column matrix format
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.to_latex(matrix_data, "b")
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 get_explanation(self):
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
- # Create LaTeX strings for multiline equation
153
- vector_a_str = r" \\ ".join([str(v) for v in vector_a])
154
- vector_b_str = r" \\ ".join([str(v) for v in vector_b])
155
- addition_str = r" \\ ".join([f"{vector_a[j]}+{vector_b[j]}" for j in range(self.dimension)])
156
- result_str = r" \\ ".join([str(v) for v in result])
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="\\vec{a} + \\vec{b}",
205
+ lhs="arrow(a) + arrow(b)", # Typst uses arrow() for vectors
163
206
  rhs=[
164
- f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
165
- f"\\begin{{bmatrix}} {addition_str} \\end{{bmatrix}}",
166
- f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
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 (original behavior)
172
- vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
173
- vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
174
- addition_str = r" \\ ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
175
- result_str = r" \\ ".join([str(v) for v in self.result])
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="\\vec{a} + \\vec{b}",
225
+ lhs="arrow(a) + arrow(b)",
180
226
  rhs=[
181
- f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
182
- f"\\begin{{bmatrix}} {addition_str} \\end{{bmatrix}}",
183
- f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
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
- vector_a_latex = self._format_vector(data['vector_a'])
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
- subparts.append(f"{scalar} \\cdot {vector_a_latex}")
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
- vector_a_latex = self._format_vector(self.vector_a)
292
- body.add_element(ContentAST.Equation(f"{self.scalar} \\cdot {vector_a_latex} = ", inline=False))
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
- self._add_single_question_answers(body)
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 get_explanation(self):
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
- # Create LaTeX strings for multiline equation
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 get_explanation(self):
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
- vector_a_latex = self._format_vector(data['vector_a'])
455
- # For magnitude, we show ||vector|| as a single string
456
- subparts.append(f"\\left\\|{vector_a_latex}\\right\\|")
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
- vector_a_latex = self._format_vector(self.vector_a)
472
- body.add_element(ContentAST.Equation(f"\\left\\|{vector_a_latex}\\right\\| = ", inline=False))
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._add_single_question_answers(body)
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 get_explanation(self):
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("\\left\\|\\vec{v}\\right\\| = \\sqrt{v_1^2 + v_2^2 + \\ldots + v_n^2}", inline=False))
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
- # Create LaTeX strings for multiline equation
493
- vector_str = r" \\ ".join([str(v) for v in vector_a])
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="\\left\\|\\vec{v}\\right\\|",
643
+ lhs="||arrow(v)||",
504
644
  rhs=[
505
- f"\\left\\|\\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}\\right\\|",
506
- f"\\sqrt{{{squares_str}}}",
507
- f"\\sqrt{{{calculation_str}}}",
508
- f"\\sqrt{{{sum_of_squares}}}",
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 the correct attributes
515
- vector_str = r" \\ ".join([str(v) for v in self.vector_a])
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="\\left\\|\\vec{v}\\right\\|",
663
+ lhs="||arrow(v)||",
524
664
  rhs=[
525
- f"\\left\\|\\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}\\right\\|",
526
- f"\\sqrt{{{squares_str}}}",
527
- f"\\sqrt{{{calculation_str}}}",
528
- f"\\sqrt{{{sum_of_squares}}}",
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.output)
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 get_body(self, **kwargs) -> ContentAST.Section:
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"].get_ast_element(label=f"Weights"),
92
+ self.answers["weights"],
88
93
  ContentAST.LineBreak(),
89
- self.answers["output"].get_ast_element(label=f"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 get_explanation(self, **kwargs) -> ContentAST.Section:
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