QuizGenerator 0.4.4__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.
Files changed (31) hide show
  1. QuizGenerator/contentast.py +949 -80
  2. QuizGenerator/generate.py +44 -7
  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 +51 -20
  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 +74 -23
  24. QuizGenerator/regenerate.py +98 -29
  25. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.0.dist-info}/METADATA +1 -1
  26. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.0.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.0.dist-info}/WHEEL +0 -0
  30. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.0.dist-info}/entry_points.txt +0 -0
  31. {quizgenerator-0.4.4.dist-info → quizgenerator-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -40,9 +40,9 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
40
40
  self.num_bits_vpn = self.num_bits_va - self.num_bits_offset
41
41
 
42
42
  self.possible_answers = {
43
- self.Target.VA_BITS: Answer.integer("answer__num_bits_va", self.num_bits_va),
44
- self.Target.OFFSET_BITS: Answer.integer("answer__num_bits_offset", self.num_bits_offset),
45
- self.Target.VPN_BITS: Answer.integer("answer__num_bits_vpn", self.num_bits_vpn)
43
+ self.Target.VA_BITS: Answer.integer("answer__num_bits_va", self.num_bits_va, unit="bits"),
44
+ self.Target.OFFSET_BITS: Answer.integer("answer__num_bits_offset", self.num_bits_offset, unit="bits"),
45
+ self.Target.VPN_BITS: Answer.integer("answer__num_bits_vpn", self.num_bits_vpn, unit="bits")
46
46
  }
47
47
 
48
48
  # Select what kind of question we are going to be
@@ -52,22 +52,25 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
52
52
 
53
53
  return
54
54
 
55
- def get_body(self, **kwargs) -> ContentAST.Section:
55
+ def _get_body(self, **kwargs):
56
+ """Build question body and collect answers."""
57
+ answers = [self.answers['answer']] # Collect the answer
58
+
56
59
  # Create table data with one blank cell
57
60
  table_data = [{}]
58
61
  for target in list(self.Target):
59
62
  if target == self.blank_kind:
60
63
  # This cell should be an answer blank
61
- table_data[0][target.value] = ContentAST.Answer(self.possible_answers[target], " bits")
64
+ table_data[0][target.value] = self.possible_answers[target]
62
65
  else:
63
66
  # This cell shows the value
64
67
  table_data[0][target.value] = f"{self.possible_answers[target].display} bits"
65
-
68
+
66
69
  table = self.create_fill_in_table(
67
70
  headers=[t.value for t in list(self.Target)],
68
71
  template_rows=table_data
69
72
  )
70
-
73
+
71
74
  body = ContentAST.Section()
72
75
  body.add_element(
73
76
  ContentAST.Paragraph(
@@ -77,11 +80,17 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
77
80
  )
78
81
  )
79
82
  body.add_element(table)
83
+ return body, answers
84
+
85
+ def get_body(self, **kwargs) -> ContentAST.Section:
86
+ """Build question body (backward compatible interface)."""
87
+ body, _ = self._get_body(**kwargs)
80
88
  return body
81
-
82
- def get_explanation(self, **kwargs) -> ContentAST.Section:
89
+
90
+ def _get_explanation(self, **kwargs):
91
+ """Build question explanation."""
83
92
  explanation = ContentAST.Section()
84
-
93
+
85
94
  explanation.add_element(
86
95
  ContentAST.Paragraph(
87
96
  [
@@ -92,7 +101,7 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
92
101
  ]
93
102
  )
94
103
  )
95
-
104
+
96
105
  explanation.add_element(
97
106
  ContentAST.Paragraph(
98
107
  [
@@ -104,7 +113,12 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
104
113
  ]
105
114
  )
106
115
  )
107
-
116
+
117
+ return explanation, []
118
+
119
+ def get_explanation(self, **kwargs) -> ContentAST.Section:
120
+ """Build question explanation (backward compatible interface)."""
121
+ explanation, _ = self._get_explanation(**kwargs)
108
122
  return explanation
109
123
 
110
124
 
@@ -245,14 +259,21 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
245
259
  self.hit_rate = 100 * number_of_hits / (self.num_requests)
246
260
  self.answers.update(
247
261
  {
248
- "answer__hit_rate": Answer.auto_float("answer__hit_rate", self.hit_rate)
262
+ "answer__hit_rate": Answer.auto_float(
263
+ "answer__hit_rate", self.hit_rate,
264
+ label=f"Hit rate, excluding non-capacity misses. Round to {Answer.DEFAULT_ROUNDING_DIGITS} decimal digits if appropriate.",
265
+ unit="%"
266
+ )
249
267
  }
250
268
  )
251
269
 
252
270
  # Return whether this workload is interesting
253
271
  return self.is_interesting()
254
272
 
255
- def get_body(self, **kwargs) -> ContentAST.Section:
273
+ def _get_body(self, **kwargs):
274
+ """Build question body and collect answers."""
275
+ answers = []
276
+
256
277
  # Create table data for cache simulation
257
278
  table_rows = []
258
279
  for request_number in sorted(self.request_results.keys()):
@@ -264,47 +285,55 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
264
285
  "Cache State": f"answer__cache_state-{request_number}" # Answer key
265
286
  }
266
287
  )
267
-
288
+ # Collect answers for this request
289
+ answers.append(self.answers[f"answer__hit-{request_number}"])
290
+ answers.append(self.answers[f"answer__evicted-{request_number}"])
291
+ answers.append(self.answers[f"answer__cache_state-{request_number}"])
292
+
268
293
  # Create table using mixin - automatically handles answer conversion
269
294
  cache_table = self.create_answer_table(
270
295
  headers=["Page Requested", "Hit/Miss", "Evicted", "Cache State"],
271
296
  data_rows=table_rows,
272
297
  answer_columns=["Hit/Miss", "Evicted", "Cache State"]
273
298
  )
274
-
299
+
300
+ # Collect hit rate answer
301
+ hit_rate_answer = self.answers["answer__hit_rate"]
302
+ answers.append(hit_rate_answer)
303
+
275
304
  # Create hit rate answer block
276
- hit_rate_block = ContentAST.AnswerBlock(
277
- ContentAST.Answer(
278
- answer=self.answers["answer__hit_rate"],
279
- label=f"Hit rate, excluding capacity misses. If appropriate, round to {Answer.DEFAULT_ROUNDING_DIGITS} decimal digits.",
280
- unit="%"
281
- )
282
- )
283
-
305
+ hit_rate_block = ContentAST.AnswerBlock(hit_rate_answer)
306
+
284
307
  # Use mixin to create complete body
285
308
  intro_text = (
286
- f"Assume we are using a <b>{self.cache_policy}</b> caching policy and a cache size of <b>{self.cache_size}</b>. "
309
+ f"Assume we are using a **{self.cache_policy}** caching policy and a cache size of **{self.cache_size}**. "
287
310
  "Given the below series of requests please fill in the table. "
288
311
  "For the hit/miss column, please write either \"hit\" or \"miss\". "
289
312
  "For the eviction column, please write either the number of the evicted page or simply a dash (e.g. \"-\")."
290
313
  )
291
-
314
+
292
315
  instructions = ContentAST.OnlyHtml([
293
316
  "For the cache state, please enter the cache contents in the order suggested in class, "
294
317
  "which means separated by commas with spaces (e.g. \"1, 2, 3\") "
295
318
  "and with the left-most being the next to be evicted. "
296
319
  "In the case where there is a tie, order by increasing number."
297
320
  ])
298
-
321
+
299
322
  body = self.create_fill_in_table_body(intro_text, instructions, cache_table)
300
323
  body.add_element(hit_rate_block)
324
+ return body, answers
325
+
326
+ def get_body(self, **kwargs) -> ContentAST.Section:
327
+ """Build question body (backward compatible interface)."""
328
+ body, _ = self._get_body(**kwargs)
301
329
  return body
302
-
303
- def get_explanation(self, **kwargs) -> ContentAST.Section:
330
+
331
+ def _get_explanation(self, **kwargs):
332
+ """Build question explanation."""
304
333
  explanation = ContentAST.Section()
305
-
334
+
306
335
  explanation.add_element(ContentAST.Paragraph(["The full caching table can be seen below."]))
307
-
336
+
308
337
  explanation.add_element(
309
338
  ContentAST.Table(
310
339
  headers=["Page", "Hit/Miss", "Evicted", "Cache State"],
@@ -319,7 +348,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
319
348
  ]
320
349
  )
321
350
  )
322
-
351
+
323
352
  explanation.add_element(
324
353
  ContentAST.Paragraph(
325
354
  [
@@ -330,7 +359,12 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
330
359
  ]
331
360
  )
332
361
  )
333
-
362
+
363
+ return explanation, []
364
+
365
+ def get_explanation(self, **kwargs) -> ContentAST.Section:
366
+ """Build question explanation (backward compatible interface)."""
367
+ explanation, _ = self._get_explanation(**kwargs)
334
368
  return explanation
335
369
 
336
370
  def is_interesting(self) -> bool:
@@ -373,22 +407,25 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
373
407
  else:
374
408
  self.answers["answer"] = Answer.string("answer__physical_address", "INVALID")
375
409
 
376
- def get_body(self) -> ContentAST.Section:
410
+ def _get_body(self):
411
+ """Build question body and collect answers."""
412
+ answers = [self.answers["answer"]]
413
+
377
414
  # Use mixin to create parameter table with answer
378
415
  parameter_info = {
379
416
  "Base": f"0x{self.base:X}",
380
417
  "Bounds": f"0x{self.bounds:X}",
381
418
  "Virtual Address": f"0x{self.virtual_address:X}"
382
419
  }
383
-
420
+
384
421
  table = self.create_parameter_answer_table(
385
422
  parameter_info=parameter_info,
386
423
  answer_label="Physical Address",
387
424
  answer_key="answer",
388
425
  transpose=True
389
426
  )
390
-
391
- return self.create_parameter_calculation_body(
427
+
428
+ body = self.create_parameter_calculation_body(
392
429
  intro_text=(
393
430
  "Given the information in the below table, "
394
431
  "please calcuate the physical address associated with the given virtual address. "
@@ -396,10 +433,17 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
396
433
  ),
397
434
  parameter_table=table
398
435
  )
399
-
400
- def get_explanation(self) -> ContentAST.Section:
436
+ return body, answers
437
+
438
+ def get_body(self) -> ContentAST.Section:
439
+ """Build question body (backward compatible interface)."""
440
+ body, _ = self._get_body()
441
+ return body
442
+
443
+ def _get_explanation(self):
444
+ """Build question explanation."""
401
445
  explanation = ContentAST.Section()
402
-
446
+
403
447
  explanation.add_element(
404
448
  ContentAST.Paragraph(
405
449
  [
@@ -410,7 +454,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
410
454
  ]
411
455
  )
412
456
  )
413
-
457
+
414
458
  explanation.add_element(
415
459
  ContentAST.Paragraph(
416
460
  [
@@ -419,7 +463,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
419
463
  ]
420
464
  )
421
465
  )
422
-
466
+
423
467
  if self.virtual_address < self.bounds:
424
468
  explanation.add_element(
425
469
  ContentAST.Paragraph(
@@ -442,7 +486,12 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
442
486
  ]
443
487
  )
444
488
  )
445
-
489
+
490
+ return explanation, []
491
+
492
+ def get_explanation(self) -> ContentAST.Section:
493
+ """Build question explanation (backward compatible interface)."""
494
+ explanation, _ = self._get_explanation()
446
495
  return explanation
447
496
 
448
497
 
@@ -540,19 +589,28 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
540
589
  self.answers["answer__physical_address"] = Answer.binary_hex(
541
590
  "answer__physical_address",
542
591
  self.physical_address,
543
- length=self.physical_bits
592
+ length=self.physical_bits,
593
+ label="Physical Address"
544
594
  )
545
595
  else:
546
596
  self.answers["answer__physical_address"] = Answer.string(
547
597
  "answer__physical_address",
548
- "INVALID"
598
+ "INVALID",
599
+ label="Physical Address"
549
600
  )
550
-
551
- self.answers["answer__segment"] = Answer.string("answer__segment", self.segment)
601
+
602
+ self.answers["answer__segment"] = Answer.string("answer__segment", self.segment,
603
+ label="Segment name")
552
604
 
553
- def get_body(self) -> ContentAST.Section:
605
+ def _get_body(self):
606
+ """Build question body and collect answers."""
607
+ answers = [
608
+ self.answers["answer__segment"],
609
+ self.answers["answer__physical_address"]
610
+ ]
611
+
554
612
  body = ContentAST.Section()
555
-
613
+
556
614
  body.add_element(
557
615
  ContentAST.Paragraph(
558
616
  [
@@ -565,39 +623,36 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
565
623
  ]
566
624
  )
567
625
  )
568
-
626
+
569
627
  # Create segment table using mixin
570
628
  segment_rows = [
571
629
  {"": "code", "base": f"0b{self.base['code']:0{self.physical_bits}b}", "bounds": f"0b{self.bounds['code']:0b}"},
572
630
  {"": "heap", "base": f"0b{self.base['heap']:0{self.physical_bits}b}", "bounds": f"0b{self.bounds['heap']:0b}"},
573
631
  {"": "stack", "base": f"0b{self.base['stack']:0{self.physical_bits}b}", "bounds": f"0b{self.bounds['stack']:0b}"}
574
632
  ]
575
-
633
+
576
634
  segment_table = self.create_answer_table(
577
635
  headers=["", "base", "bounds"],
578
636
  data_rows=segment_rows,
579
637
  answer_columns=[] # No answer columns in this table
580
638
  )
581
-
639
+
582
640
  body.add_element(segment_table)
583
-
641
+
584
642
  body.add_element(
585
- ContentAST.AnswerBlock(
586
- [
587
- ContentAST.Answer(
588
- self.answers["answer__segment"],
589
- label="Segment name"
590
- ),
591
- ContentAST.Answer(
592
- self.answers["answer__physical_address"],
593
- label="Physical Address"
594
- )
595
- ]
596
- )
643
+ ContentAST.AnswerBlock([
644
+ self.answers["answer__segment"],
645
+ self.answers["answer__physical_address"]
646
+ ])
597
647
  )
648
+ return body, answers
649
+
650
+ def get_body(self) -> ContentAST.Section:
651
+ """Build question body (backward compatible interface)."""
652
+ body, _ = self._get_body()
598
653
  return body
599
-
600
- def get_explanation(self) -> ContentAST.Section:
654
+
655
+ def _get_explanation(self):
601
656
  explanation = ContentAST.Section()
602
657
 
603
658
  explanation.add_element(
@@ -691,7 +746,12 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
691
746
  f" 0b{self.physical_address:0{self.physical_bits}b}\n"
692
747
  )
693
748
  )
694
-
749
+
750
+ return explanation, []
751
+
752
+ def get_explanation(self) -> ContentAST.Section:
753
+ """Build question explanation (backward compatible interface)."""
754
+ explanation, _ = self._get_explanation()
695
755
  return explanation
696
756
 
697
757
 
@@ -765,34 +825,45 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
765
825
 
766
826
  self.answers.update(
767
827
  {
768
- "answer__vpn": Answer.binary_hex("answer__vpn", self.vpn, length=self.num_bits_vpn),
769
- "answer__offset": Answer.binary_hex("answer__offset", self.offset, length=self.num_bits_offset),
770
- "answer__pte": Answer.binary_hex("answer__pte", self.pte, length=(self.num_bits_pfn + 1)),
828
+ "answer__vpn": Answer.binary_hex("answer__vpn", self.vpn, length=self.num_bits_vpn, label="VPN"),
829
+ "answer__offset": Answer.binary_hex("answer__offset", self.offset, length=self.num_bits_offset, label="Offset"),
830
+ "answer__pte": Answer.binary_hex("answer__pte", self.pte, length=(self.num_bits_pfn + 1), label="PTE"),
771
831
  }
772
832
  )
773
833
 
774
834
  if self.is_valid:
775
835
  self.answers.update(
776
836
  {
777
- "answer__is_valid": Answer.string("answer__is_valid", "VALID"),
778
- "answer__pfn": Answer.binary_hex("answer__pfn", self.pfn, length=self.num_bits_pfn),
837
+ "answer__is_valid": Answer.string("answer__is_valid", "VALID", label="VALID or INVALID?"),
838
+ "answer__pfn": Answer.binary_hex("answer__pfn", self.pfn, length=self.num_bits_pfn, label="PFN"),
779
839
  "answer__physical_address": Answer.binary_hex(
780
- "answer__physical_address", self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset)
840
+ "answer__physical_address", self.physical_address,
841
+ length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
781
842
  ),
782
843
  }
783
844
  )
784
845
  else:
785
846
  self.answers.update(
786
847
  {
787
- "answer__is_valid": Answer.string("answer__is_valid", "INVALID"),
788
- "answer__pfn": Answer.string("answer__pfn", "INVALID"),
789
- "answer__physical_address": Answer.string("answer__physical_address", "INVALID"),
848
+ "answer__is_valid": Answer.string("answer__is_valid", "INVALID", label="VALID or INVALID?"),
849
+ "answer__pfn": Answer.string("answer__pfn", "INVALID", label="PFN"),
850
+ "answer__physical_address": Answer.string("answer__physical_address", "INVALID", label="Physical Address"),
790
851
  }
791
852
  )
792
853
 
793
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
854
+ def _get_body(self, *args, **kwargs):
855
+ """Build question body and collect answers."""
856
+ answers = [
857
+ self.answers["answer__vpn"],
858
+ self.answers["answer__offset"],
859
+ self.answers["answer__pte"],
860
+ self.answers["answer__is_valid"],
861
+ self.answers["answer__pfn"],
862
+ self.answers["answer__physical_address"],
863
+ ]
864
+
794
865
  body = ContentAST.Section()
795
-
866
+
796
867
  body.add_element(
797
868
  ContentAST.Paragraph(
798
869
  [
@@ -801,14 +872,14 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
801
872
  ]
802
873
  )
803
874
  )
804
-
875
+
805
876
  # Create parameter info table using mixin
806
877
  parameter_info = {
807
878
  "Virtual Address": f"0b{self.virtual_address:0{self.num_bits_vpn + self.num_bits_offset}b}",
808
879
  "# VPN bits": f"{self.num_bits_vpn}",
809
880
  "# PFN bits": f"{self.num_bits_pfn}"
810
881
  }
811
-
882
+
812
883
  body.add_element(self.create_info_table(parameter_info))
813
884
 
814
885
  # Use the page table generated in refresh() for deterministic output
@@ -827,33 +898,36 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
827
898
 
828
899
  if (max(self.page_table.keys()) + 1) != 2 ** self.num_bits_vpn:
829
900
  value_matrix.append(["...", "..."])
830
-
901
+
831
902
  body.add_element(
832
903
  ContentAST.Table(
833
904
  headers=["VPN", "PTE"],
834
905
  data=value_matrix
835
906
  )
836
907
  )
837
-
908
+
838
909
  body.add_element(
839
- ContentAST.AnswerBlock(
840
- [
841
-
842
- ContentAST.Answer(self.answers["answer__vpn"], label="VPN"),
843
- ContentAST.Answer(self.answers["answer__offset"], label="Offset"),
844
- ContentAST.Answer(self.answers["answer__pte"], label="PTE"),
845
- ContentAST.Answer(self.answers["answer__is_valid"], label="VALID or INVALID?"),
846
- ContentAST.Answer(self.answers["answer__pfn"], label="PFN"),
847
- ContentAST.Answer(self.answers["answer__physical_address"], label="Physical Address"),
848
- ]
849
- )
910
+ ContentAST.AnswerBlock([
911
+ self.answers["answer__vpn"],
912
+ self.answers["answer__offset"],
913
+ self.answers["answer__pte"],
914
+ self.answers["answer__is_valid"],
915
+ self.answers["answer__pfn"],
916
+ self.answers["answer__physical_address"],
917
+ ])
850
918
  )
851
-
919
+
920
+ return body, answers
921
+
922
+ def get_body(self, *args, **kwargs) -> ContentAST.Section:
923
+ """Build question body (backward compatible interface)."""
924
+ body, _ = self._get_body(*args, **kwargs)
852
925
  return body
853
926
 
854
- def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
927
+ def _get_explanation(self, *args, **kwargs):
928
+ """Build question explanation."""
855
929
  explanation = ContentAST.Section()
856
-
930
+
857
931
  explanation.add_element(
858
932
  ContentAST.Paragraph(
859
933
  [
@@ -863,7 +937,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
863
937
  ]
864
938
  )
865
939
  )
866
-
940
+
867
941
  explanation.add_element(
868
942
  ContentAST.Paragraph(
869
943
  [
@@ -871,7 +945,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
871
945
  ]
872
946
  )
873
947
  )
874
-
948
+
875
949
  explanation.add_element(
876
950
  ContentAST.Paragraph(
877
951
  [
@@ -881,7 +955,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
881
955
  ]
882
956
  )
883
957
  )
884
-
958
+
885
959
  explanation.add_element(
886
960
  ContentAST.Paragraph(
887
961
  [
@@ -892,12 +966,12 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
892
966
  ]
893
967
  )
894
968
  )
895
-
969
+
896
970
  if self.is_valid:
897
971
  explanation.add_element(
898
972
  ContentAST.Paragraph(
899
973
  [
900
- f"In our PTE we see that the first bit is <b>{self.pte // (2 ** self.num_bits_pfn)}</b> meaning that the translation is <b>VALID</b>"
974
+ f"In our PTE we see that the first bit is **{self.pte // (2 ** self.num_bits_pfn)}** meaning that the translation is **VALID**"
901
975
  ]
902
976
  )
903
977
  )
@@ -905,14 +979,14 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
905
979
  explanation.add_element(
906
980
  ContentAST.Paragraph(
907
981
  [
908
- f"In our PTE we see that the first bit is <b>{self.pte // (2 ** self.num_bits_pfn)}</b> meaning that the translation is <b>INVALID</b>.",
982
+ f"In our PTE we see that the first bit is **{self.pte // (2 ** self.num_bits_pfn)}** meaning that the translation is **INVALID**.",
909
983
  "Therefore, we just write \"INVALID\" as our answer.",
910
984
  "If it were valid we would complete the below steps.",
911
985
  "<hr>"
912
986
  ]
913
987
  )
914
988
  )
915
-
989
+
916
990
  explanation.add_element(
917
991
  ContentAST.Paragraph(
918
992
  [
@@ -930,7 +1004,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
930
1004
  f"\\& \\texttt{{0b{(2 ** self.num_bits_pfn) - 1:0{self.num_bits_pfn + 1}b}}}"
931
1005
  )
932
1006
  )
933
-
1007
+
934
1008
  explanation.add_elements(
935
1009
  [
936
1010
  ContentAST.Paragraph(
@@ -944,7 +1018,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
944
1018
  )
945
1019
  ]
946
1020
  )
947
-
1021
+
948
1022
  explanation.add_elements(
949
1023
  [
950
1024
  ContentAST.Paragraph(["Note: Strictly speaking, this calculation is:", ]),
@@ -955,6 +1029,11 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
955
1029
  ]
956
1030
  )
957
1031
 
1032
+ return explanation, []
1033
+
1034
+ def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
1035
+ """Build question explanation (backward compatible interface)."""
1036
+ explanation, _ = self._get_explanation(*args, **kwargs)
958
1037
  return explanation
959
1038
 
960
1039
 
@@ -1108,49 +1187,73 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1108
1187
 
1109
1188
  # Set up answers
1110
1189
  self.answers.update({
1111
- "answer__pdi": Answer.binary_hex("answer__pdi", self.pdi, length=self.num_bits_pdi),
1112
- "answer__pti": Answer.binary_hex("answer__pti", self.pti, length=self.num_bits_pti),
1113
- "answer__offset": Answer.binary_hex("answer__offset", self.offset, length=self.num_bits_offset),
1114
- "answer__pd_entry": Answer.binary_hex("answer__pd_entry", self.pd_entry, length=(self.num_bits_pfn + 1)),
1115
- "answer__pt_number": Answer.binary_hex("answer__pt_number", self.page_table_number, length=self.num_bits_pfn) if self.pd_valid else Answer.string("answer__pt_number", "INVALID"),
1190
+ "answer__pdi": Answer.binary_hex("answer__pdi", self.pdi, length=self.num_bits_pdi,
1191
+ label="PDI (Page Directory Index)"),
1192
+ "answer__pti": Answer.binary_hex("answer__pti", self.pti, length=self.num_bits_pti,
1193
+ label="PTI (Page Table Index)"),
1194
+ "answer__offset": Answer.binary_hex("answer__offset", self.offset, length=self.num_bits_offset,
1195
+ label="Offset"),
1196
+ "answer__pd_entry": Answer.binary_hex("answer__pd_entry", self.pd_entry, length=(self.num_bits_pfn + 1),
1197
+ label="PD Entry (from Page Directory)"),
1198
+ "answer__pt_number": (
1199
+ Answer.binary_hex("answer__pt_number", self.page_table_number, length=self.num_bits_pfn,
1200
+ label="Page Table Number")
1201
+ if self.pd_valid
1202
+ else Answer.string("answer__pt_number", "INVALID", label="Page Table Number")
1203
+ ),
1116
1204
  })
1117
1205
 
1118
1206
  # PTE answer: if PD is valid, accept the actual PTE value from the table
1119
1207
  # (regardless of whether that PTE is valid or invalid)
1120
1208
  if self.pd_valid:
1121
1209
  self.answers.update({
1122
- "answer__pte": Answer.binary_hex("answer__pte", self.pte, length=(self.num_bits_pfn + 1)),
1210
+ "answer__pte": Answer.binary_hex("answer__pte", self.pte, length=(self.num_bits_pfn + 1),
1211
+ label="PTE (from Page Table)"),
1123
1212
  })
1124
1213
  else:
1125
1214
  # If PD is invalid, student can't look up the page table
1126
1215
  # Accept both "INVALID" (for consistency) and "N/A" (for accuracy)
1127
1216
  self.answers.update({
1128
- "answer__pte": Answer.string("answer__pte", ["INVALID", "N/A"]),
1217
+ "answer__pte": Answer.string("answer__pte", ["INVALID", "N/A"], label="PTE (from Page Table)"),
1129
1218
  })
1130
1219
 
1131
1220
  # Validity, PFN, and Physical Address depend on BOTH levels being valid
1132
1221
  if self.pd_valid and self.pt_valid:
1133
1222
  self.answers.update({
1134
- "answer__is_valid": Answer.string("answer__is_valid", "VALID"),
1135
- "answer__pfn": Answer.binary_hex("answer__pfn", self.pfn, length=self.num_bits_pfn),
1223
+ "answer__is_valid": Answer.string("answer__is_valid", "VALID", label="VALID or INVALID?"),
1224
+ "answer__pfn": Answer.binary_hex("answer__pfn", self.pfn, length=self.num_bits_pfn, label="PFN"),
1136
1225
  "answer__physical_address": Answer.binary_hex(
1137
- "answer__physical_address", self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset)
1226
+ "answer__physical_address", self.physical_address,
1227
+ length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
1138
1228
  ),
1139
1229
  })
1140
1230
  else:
1141
1231
  self.answers.update({
1142
- "answer__is_valid": Answer.string("answer__is_valid", "INVALID"),
1143
- "answer__pfn": Answer.string("answer__pfn", "INVALID"),
1144
- "answer__physical_address": Answer.string("answer__physical_address", "INVALID"),
1232
+ "answer__is_valid": Answer.string("answer__is_valid", "INVALID", label="VALID or INVALID?"),
1233
+ "answer__pfn": Answer.string("answer__pfn", "INVALID", label="PFN"),
1234
+ "answer__physical_address": Answer.string("answer__physical_address", "INVALID", label="Physical Address"),
1145
1235
  })
1146
1236
 
1147
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
1237
+ def _get_body(self, *args, **kwargs):
1238
+ """Build question body and collect answers."""
1239
+ answers = [
1240
+ self.answers["answer__pdi"],
1241
+ self.answers["answer__pti"],
1242
+ self.answers["answer__offset"],
1243
+ self.answers["answer__pd_entry"],
1244
+ self.answers["answer__pt_number"],
1245
+ self.answers["answer__pte"],
1246
+ self.answers["answer__is_valid"],
1247
+ self.answers["answer__pfn"],
1248
+ self.answers["answer__physical_address"],
1249
+ ]
1250
+
1148
1251
  body = ContentAST.Section()
1149
1252
 
1150
1253
  body.add_element(
1151
1254
  ContentAST.Paragraph([
1152
1255
  "Given the below information please calculate the equivalent physical address of the given virtual address, filling out all steps along the way.",
1153
- "This problem uses <b>two-level (hierarchical) paging</b>.",
1256
+ "This problem uses **two-level (hierarchical) paging**.",
1154
1257
  "Remember, we typically have the MSB representing valid or invalid."
1155
1258
  ])
1156
1259
  )
@@ -1245,21 +1348,27 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1245
1348
  # Answer block
1246
1349
  body.add_element(
1247
1350
  ContentAST.AnswerBlock([
1248
- ContentAST.Answer(self.answers["answer__pdi"], label="PDI (Page Directory Index)"),
1249
- ContentAST.Answer(self.answers["answer__pti"], label="PTI (Page Table Index)"),
1250
- ContentAST.Answer(self.answers["answer__offset"], label="Offset"),
1251
- ContentAST.Answer(self.answers["answer__pd_entry"], label="PD Entry (from Page Directory)"),
1252
- ContentAST.Answer(self.answers["answer__pt_number"], label="Page Table Number"),
1253
- ContentAST.Answer(self.answers["answer__pte"], label="PTE (from Page Table)"),
1254
- ContentAST.Answer(self.answers["answer__is_valid"], label="VALID or INVALID?"),
1255
- ContentAST.Answer(self.answers["answer__pfn"], label="PFN"),
1256
- ContentAST.Answer(self.answers["answer__physical_address"], label="Physical Address"),
1351
+ self.answers["answer__pdi"],
1352
+ self.answers["answer__pti"],
1353
+ self.answers["answer__offset"],
1354
+ self.answers["answer__pd_entry"],
1355
+ self.answers["answer__pt_number"],
1356
+ self.answers["answer__pte"],
1357
+ self.answers["answer__is_valid"],
1358
+ self.answers["answer__pfn"],
1359
+ self.answers["answer__physical_address"],
1257
1360
  ])
1258
1361
  )
1259
1362
 
1363
+ return body, answers
1364
+
1365
+ def get_body(self, *args, **kwargs) -> ContentAST.Section:
1366
+ """Build question body (backward compatible interface)."""
1367
+ body, _ = self._get_body(*args, **kwargs)
1260
1368
  return body
1261
1369
 
1262
- def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
1370
+ def _get_explanation(self, *args, **kwargs):
1371
+ """Build question explanation."""
1263
1372
  explanation = ContentAST.Section()
1264
1373
 
1265
1374
  explanation.add_element(
@@ -1278,7 +1387,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1278
1387
  # Step 1: Extract PDI, PTI, Offset
1279
1388
  explanation.add_element(
1280
1389
  ContentAST.Paragraph([
1281
- f"<b>Step 1: Extract components from Virtual Address</b>",
1390
+ f"**Step 1: Extract components from Virtual Address**",
1282
1391
  f"Virtual Address = PDI | PTI | Offset",
1283
1392
  f"<tt>0b{self.virtual_address:0{self.num_bits_vpn + self.num_bits_offset}b}</tt> = "
1284
1393
  f"<tt>0b{self.pdi:0{self.num_bits_pdi}b}</tt> | "
@@ -1290,7 +1399,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1290
1399
  # Step 2: Look up PD Entry
1291
1400
  explanation.add_element(
1292
1401
  ContentAST.Paragraph([
1293
- f"<b>Step 2: Look up Page Directory Entry</b>",
1402
+ f"**Step 2: Look up Page Directory Entry**",
1294
1403
  f"Using PDI = <tt>0b{self.pdi:0{self.num_bits_pdi}b}</tt>, we find PD Entry = <tt>0b{self.pd_entry:0{self.num_bits_pfn + 1}b}</tt>"
1295
1404
  ])
1296
1405
  )
@@ -1299,8 +1408,8 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1299
1408
  pd_valid_bit = self.pd_entry // (2 ** self.num_bits_pfn)
1300
1409
  explanation.add_element(
1301
1410
  ContentAST.Paragraph([
1302
- f"<b>Step 3: Check Page Directory Entry validity</b>",
1303
- f"The MSB (valid bit) is <b>{pd_valid_bit}</b>, so this PD Entry is <b>{'VALID' if self.pd_valid else 'INVALID'}</b>."
1411
+ f"**Step 3: Check Page Directory Entry validity**",
1412
+ f"The MSB (valid bit) is **{pd_valid_bit}**, so this PD Entry is **{'VALID' if self.pd_valid else 'INVALID'}**."
1304
1413
  ])
1305
1414
  )
1306
1415
 
@@ -1308,7 +1417,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1308
1417
  explanation.add_element(
1309
1418
  ContentAST.Paragraph([
1310
1419
  "Since the Page Directory Entry is invalid, the translation fails here.",
1311
- "We write <b>INVALID</b> for all remaining fields.",
1420
+ "We write **INVALID** for all remaining fields.",
1312
1421
  "If it were valid, we would continue with the steps below.",
1313
1422
  "<hr>"
1314
1423
  ])
@@ -1317,7 +1426,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1317
1426
  # Step 4: Extract PT number (if PD valid)
1318
1427
  explanation.add_element(
1319
1428
  ContentAST.Paragraph([
1320
- f"<b>Step 4: Extract Page Table Number</b>",
1429
+ f"**Step 4: Extract Page Table Number**",
1321
1430
  "We remove the valid bit from the PD Entry to get the Page Table Number:"
1322
1431
  ])
1323
1432
  )
@@ -1333,14 +1442,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1333
1442
  if self.pd_valid:
1334
1443
  explanation.add_element(
1335
1444
  ContentAST.Paragraph([
1336
- f"This tells us to use <b>Page Table #{self.page_table_number}</b>."
1445
+ f"This tells us to use **Page Table #{self.page_table_number}**."
1337
1446
  ])
1338
1447
  )
1339
1448
 
1340
1449
  # Step 5: Look up PTE
1341
1450
  explanation.add_element(
1342
1451
  ContentAST.Paragraph([
1343
- f"<b>Step 5: Look up Page Table Entry</b>",
1452
+ f"**Step 5: Look up Page Table Entry**",
1344
1453
  f"Using PTI = <tt>0b{self.pti:0{self.num_bits_pti}b}</tt> in Page Table #{self.page_table_number}, "
1345
1454
  f"we find PTE = <tt>0b{self.pte:0{self.num_bits_pfn + 1}b}</tt>"
1346
1455
  ])
@@ -1350,8 +1459,8 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1350
1459
  pt_valid_bit = self.pte // (2 ** self.num_bits_pfn)
1351
1460
  explanation.add_element(
1352
1461
  ContentAST.Paragraph([
1353
- f"<b>Step 6: Check Page Table Entry validity</b>",
1354
- f"The MSB (valid bit) is <b>{pt_valid_bit}</b>, so this PTE is <b>{'VALID' if self.pt_valid else 'INVALID'}</b>."
1462
+ f"**Step 6: Check Page Table Entry validity**",
1463
+ f"The MSB (valid bit) is **{pt_valid_bit}**, so this PTE is **{'VALID' if self.pt_valid else 'INVALID'}**."
1355
1464
  ])
1356
1465
  )
1357
1466
 
@@ -1359,7 +1468,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1359
1468
  explanation.add_element(
1360
1469
  ContentAST.Paragraph([
1361
1470
  "Since the Page Table Entry is invalid, the translation fails.",
1362
- "We write <b>INVALID</b> for PFN and Physical Address.",
1471
+ "We write **INVALID** for PFN and Physical Address.",
1363
1472
  "If it were valid, we would continue with the steps below.",
1364
1473
  "<hr>"
1365
1474
  ])
@@ -1368,7 +1477,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1368
1477
  # Step 7: Extract PFN
1369
1478
  explanation.add_element(
1370
1479
  ContentAST.Paragraph([
1371
- f"<b>Step 7: Extract PFN</b>",
1480
+ f"**Step 7: Extract PFN**",
1372
1481
  "We remove the valid bit from the PTE to get the PFN:"
1373
1482
  ])
1374
1483
  )
@@ -1384,7 +1493,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1384
1493
  # Step 8: Construct physical address
1385
1494
  explanation.add_element(
1386
1495
  ContentAST.Paragraph([
1387
- f"<b>Step 8: Construct Physical Address</b>",
1496
+ f"**Step 8: Construct Physical Address**",
1388
1497
  "Physical Address = PFN | Offset"
1389
1498
  ])
1390
1499
  )
@@ -1397,4 +1506,9 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1397
1506
  )
1398
1507
  )
1399
1508
 
1509
+ return explanation, []
1510
+
1511
+ def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
1512
+ """Build question explanation (backward compatible interface)."""
1513
+ explanation, _ = self._get_explanation(*args, **kwargs)
1400
1514
  return explanation