QuizGenerator 0.6.2__py3-none-any.whl → 0.7.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 (30) hide show
  1. QuizGenerator/contentast.py +2198 -2213
  2. QuizGenerator/misc.py +1 -1
  3. QuizGenerator/mixins.py +64 -64
  4. QuizGenerator/premade_questions/basic.py +16 -16
  5. QuizGenerator/premade_questions/cst334/languages.py +26 -26
  6. QuizGenerator/premade_questions/cst334/math_questions.py +42 -42
  7. QuizGenerator/premade_questions/cst334/memory_questions.py +124 -124
  8. QuizGenerator/premade_questions/cst334/persistence_questions.py +48 -48
  9. QuizGenerator/premade_questions/cst334/process.py +38 -38
  10. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +45 -45
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +34 -34
  12. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +53 -53
  13. QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
  14. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +65 -65
  15. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +39 -39
  16. QuizGenerator/premade_questions/cst463/models/attention.py +36 -36
  17. QuizGenerator/premade_questions/cst463/models/cnns.py +26 -26
  18. QuizGenerator/premade_questions/cst463/models/rnns.py +36 -36
  19. QuizGenerator/premade_questions/cst463/models/text.py +32 -32
  20. QuizGenerator/premade_questions/cst463/models/weight_counting.py +15 -15
  21. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +124 -124
  22. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +161 -161
  23. QuizGenerator/question.py +41 -41
  24. QuizGenerator/quiz.py +7 -7
  25. QuizGenerator/typst_utils.py +2 -2
  26. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/METADATA +1 -1
  27. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/RECORD +30 -30
  28. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/WHEEL +0 -0
  29. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/entry_points.txt +0 -0
  30. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,7 @@ import logging
9
9
  import math
10
10
  from typing import List, Optional
11
11
 
12
- from QuizGenerator.contentast import ContentAST, AnswerTypes
12
+ import QuizGenerator.contentast as ca
13
13
  from QuizGenerator.question import Question, QuestionRegistry, RegenerableChoiceMixin
14
14
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
15
15
 
@@ -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: AnswerTypes.Int(self.num_bits_va, unit="bits"),
44
- self.Target.OFFSET_BITS: AnswerTypes.Int(self.num_bits_offset, unit="bits"),
45
- self.Target.VPN_BITS: AnswerTypes.Int(self.num_bits_vpn, unit="bits")
43
+ self.Target.VA_BITS: ca.AnswerTypes.Int(self.num_bits_va, unit="bits"),
44
+ self.Target.OFFSET_BITS: ca.AnswerTypes.Int(self.num_bits_offset, unit="bits"),
45
+ self.Target.VPN_BITS: ca.AnswerTypes.Int(self.num_bits_vpn, unit="bits")
46
46
  }
47
47
 
48
48
  # Select what kind of question we are going to be
@@ -71,9 +71,9 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
71
71
  template_rows=table_data
72
72
  )
73
73
 
74
- body = ContentAST.Section()
74
+ body = ca.Section()
75
75
  body.add_element(
76
- ContentAST.Paragraph(
76
+ ca.Paragraph(
77
77
  [
78
78
  "Given the information in the below table, please complete the table as appropriate."
79
79
  ]
@@ -82,17 +82,17 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
82
82
  body.add_element(table)
83
83
  return body, answers
84
84
 
85
- def get_body(self, **kwargs) -> ContentAST.Section:
85
+ def get_body(self, **kwargs) -> ca.Section:
86
86
  """Build question body (backward compatible interface)."""
87
87
  body, _ = self._get_body(**kwargs)
88
88
  return body
89
89
 
90
90
  def _get_explanation(self, **kwargs):
91
91
  """Build question explanation."""
92
- explanation = ContentAST.Section()
92
+ explanation = ca.Section()
93
93
 
94
94
  explanation.add_element(
95
- ContentAST.Paragraph(
95
+ ca.Paragraph(
96
96
  [
97
97
  "Remember, when we are calculating the size of virtual address spaces, "
98
98
  "the number of bits in the overall address space is equal to the number of bits in the VPN "
@@ -103,20 +103,20 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
103
103
  )
104
104
 
105
105
  explanation.add_element(
106
- ContentAST.Paragraph(
106
+ ca.Paragraph(
107
107
  [
108
- ContentAST.Text(f"{self.num_bits_va}", emphasis=(self.blank_kind == self.Target.VA_BITS)),
109
- ContentAST.Text(" = "),
110
- ContentAST.Text(f"{self.num_bits_vpn}", emphasis=(self.blank_kind == self.Target.VPN_BITS)),
111
- ContentAST.Text(" + "),
112
- ContentAST.Text(f"{self.num_bits_offset}", emphasis=(self.blank_kind == self.Target.OFFSET_BITS))
108
+ ca.Text(f"{self.num_bits_va}", emphasis=(self.blank_kind == self.Target.VA_BITS)),
109
+ ca.Text(" = "),
110
+ ca.Text(f"{self.num_bits_vpn}", emphasis=(self.blank_kind == self.Target.VPN_BITS)),
111
+ ca.Text(" + "),
112
+ ca.Text(f"{self.num_bits_offset}", emphasis=(self.blank_kind == self.Target.OFFSET_BITS))
113
113
  ]
114
114
  )
115
115
  )
116
116
 
117
117
  return explanation, []
118
118
 
119
- def get_explanation(self, **kwargs) -> ContentAST.Section:
119
+ def get_explanation(self, **kwargs) -> ca.Section:
120
120
  """Build question explanation (backward compatible interface)."""
121
121
  explanation, _ = self._get_explanation(**kwargs)
122
122
  return explanation
@@ -245,16 +245,16 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
245
245
 
246
246
  self.answers.update(
247
247
  {
248
- f"answer__hit-{request_number}": AnswerTypes.String(('hit' if was_hit else 'miss')),
249
- f"answer__evicted-{request_number}": AnswerTypes.String(('-' if evicted is None else f"{evicted}")),
250
- f"answer__cache_state-{request_number}": AnswerTypes.List(value=copy.copy(cache_state), order_matters=True),
248
+ f"answer__hit-{request_number}": ca.AnswerTypes.String(('hit' if was_hit else 'miss')),
249
+ f"answer__evicted-{request_number}": ca.AnswerTypes.String(('-' if evicted is None else f"{evicted}")),
250
+ f"answer__cache_state-{request_number}": ca.AnswerTypes.List(value=copy.copy(cache_state), order_matters=True),
251
251
  }
252
252
  )
253
253
 
254
254
  self.hit_rate = 100 * number_of_hits / (self.num_requests)
255
255
  self.answers.update(
256
256
  {
257
- "answer__hit_rate": AnswerTypes.Float(self.hit_rate,
257
+ "answer__hit_rate": ca.AnswerTypes.Float(self.hit_rate,
258
258
  label=f"Hit rate, excluding non-capacity misses",
259
259
  unit="%"
260
260
  )
@@ -296,7 +296,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
296
296
  answers.append(hit_rate_answer)
297
297
 
298
298
  # Create hit rate answer block
299
- hit_rate_block = ContentAST.AnswerBlock(hit_rate_answer)
299
+ hit_rate_block = ca.AnswerBlock(hit_rate_answer)
300
300
 
301
301
  # Use mixin to create complete body
302
302
  intro_text = (
@@ -306,7 +306,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
306
306
  "For the eviction column, please write either the number of the evicted page or simply a dash (e.g. \"-\")."
307
307
  )
308
308
 
309
- instructions = ContentAST.OnlyHtml([
309
+ instructions = ca.OnlyHtml([
310
310
  "For the cache state, please enter the cache contents in the order suggested in class, "
311
311
  "which means separated by commas with spaces (e.g. \"1, 2, 3\") "
312
312
  "and with the left-most being the next to be evicted. "
@@ -317,19 +317,19 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
317
317
  body.add_element(hit_rate_block)
318
318
  return body, answers
319
319
 
320
- def get_body(self, **kwargs) -> ContentAST.Section:
320
+ def get_body(self, **kwargs) -> ca.Section:
321
321
  """Build question body (backward compatible interface)."""
322
322
  body, _ = self._get_body(**kwargs)
323
323
  return body
324
324
 
325
325
  def _get_explanation(self, **kwargs):
326
326
  """Build question explanation."""
327
- explanation = ContentAST.Section()
327
+ explanation = ca.Section()
328
328
 
329
- explanation.add_element(ContentAST.Paragraph(["The full caching table can be seen below."]))
329
+ explanation.add_element(ca.Paragraph(["The full caching table can be seen below."]))
330
330
 
331
331
  explanation.add_element(
332
- ContentAST.Table(
332
+ ca.Table(
333
333
  headers=["Page", "Hit/Miss", "Evicted", "Cache State"],
334
334
  data=[
335
335
  [
@@ -344,7 +344,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
344
344
  )
345
345
 
346
346
  explanation.add_element(
347
- ContentAST.Paragraph(
347
+ ca.Paragraph(
348
348
  [
349
349
  "To calculate the hit rate we calculate the percentage of requests "
350
350
  "that were cache hits out of the total number of requests. "
@@ -356,7 +356,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
356
356
 
357
357
  return explanation, []
358
358
 
359
- def get_explanation(self, **kwargs) -> ContentAST.Section:
359
+ def get_explanation(self, **kwargs) -> ca.Section:
360
360
  """Build question explanation (backward compatible interface)."""
361
361
  explanation, _ = self._get_explanation(**kwargs)
362
362
  return explanation
@@ -393,12 +393,12 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
393
393
  self.virtual_address = self.rng.randint(1, int(self.bounds / self.PROBABILITY_OF_VALID))
394
394
 
395
395
  if self.virtual_address < self.bounds:
396
- self.answers["answer"] = AnswerTypes.Hex(
396
+ self.answers["answer"] = ca.AnswerTypes.Hex(
397
397
  self.base + self.virtual_address,
398
398
  length=math.ceil(math.log2(self.base + self.virtual_address))
399
399
  )
400
400
  else:
401
- self.answers["answer"] = AnswerTypes.String("INVALID")
401
+ self.answers["answer"] = ca.AnswerTypes.String("INVALID")
402
402
 
403
403
  def _get_body(self):
404
404
  """Build question body and collect answers."""
@@ -428,17 +428,17 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
428
428
  )
429
429
  return body, answers
430
430
 
431
- def get_body(self) -> ContentAST.Section:
431
+ def get_body(self) -> ca.Section:
432
432
  """Build question body (backward compatible interface)."""
433
433
  body, _ = self._get_body()
434
434
  return body
435
435
 
436
436
  def _get_explanation(self):
437
437
  """Build question explanation."""
438
- explanation = ContentAST.Section()
438
+ explanation = ca.Section()
439
439
 
440
440
  explanation.add_element(
441
- ContentAST.Paragraph(
441
+ ca.Paragraph(
442
442
  [
443
443
  "There's two steps to figuring out base and bounds.",
444
444
  "1. Are we within the bounds?\n",
@@ -449,7 +449,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
449
449
  )
450
450
 
451
451
  explanation.add_element(
452
- ContentAST.Paragraph(
452
+ ca.Paragraph(
453
453
  [
454
454
  f"Step 1: 0x{self.virtual_address:X} < 0x{self.bounds:X} "
455
455
  f"--> {'***VALID***' if (self.virtual_address < self.bounds) else 'INVALID'}"
@@ -459,7 +459,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
459
459
 
460
460
  if self.virtual_address < self.bounds:
461
461
  explanation.add_element(
462
- ContentAST.Paragraph(
462
+ ca.Paragraph(
463
463
  [
464
464
  f"Step 2: Since the previous check passed, we calculate "
465
465
  f"0x{self.base:X} + 0x{self.virtual_address:X} "
@@ -470,7 +470,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
470
470
  )
471
471
  else:
472
472
  explanation.add_element(
473
- ContentAST.Paragraph(
473
+ ca.Paragraph(
474
474
  [
475
475
  f"Step 2: Since the previous check failed, we simply write ***INVALID***.",
476
476
  "***If*** it had been valid, we would have calculated "
@@ -482,7 +482,7 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
482
482
 
483
483
  return explanation, []
484
484
 
485
- def get_explanation(self) -> ContentAST.Section:
485
+ def get_explanation(self) -> ca.Section:
486
486
  """Build question explanation (backward compatible interface)."""
487
487
  explanation, _ = self._get_explanation()
488
488
  return explanation
@@ -579,15 +579,15 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
579
579
 
580
580
  # Set answers based on whether it's in bounds or not
581
581
  if self.__within_bounds(self.segment, self.offset, self.bounds[self.segment]):
582
- self.answers["answer__physical_address"] = AnswerTypes.Binary(
582
+ self.answers["answer__physical_address"] = ca.AnswerTypes.Binary(
583
583
  self.physical_address,
584
584
  length=self.physical_bits,
585
585
  label="Physical Address"
586
586
  )
587
587
  else:
588
- self.answers["answer__physical_address"] = AnswerTypes.String("INVALID", label="Physical Address")
588
+ self.answers["answer__physical_address"] = ca.AnswerTypes.String("INVALID", label="Physical Address")
589
589
 
590
- self.answers["answer__segment"] = AnswerTypes.String(self.segment, label="Segment name")
590
+ self.answers["answer__segment"] = ca.AnswerTypes.String(self.segment, label="Segment name")
591
591
 
592
592
  def _get_body(self):
593
593
  """Build question body and collect answers."""
@@ -596,10 +596,10 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
596
596
  self.answers["answer__physical_address"]
597
597
  ]
598
598
 
599
- body = ContentAST.Section()
599
+ body = ca.Section()
600
600
 
601
601
  body.add_element(
602
- ContentAST.Paragraph(
602
+ ca.Paragraph(
603
603
  [
604
604
  f"Given a virtual address space of {self.virtual_bits}bits, "
605
605
  f"and a physical address space of {self.physical_bits}bits, "
@@ -627,23 +627,23 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
627
627
  body.add_element(segment_table)
628
628
 
629
629
  body.add_element(
630
- ContentAST.AnswerBlock([
630
+ ca.AnswerBlock([
631
631
  self.answers["answer__segment"],
632
632
  self.answers["answer__physical_address"]
633
633
  ])
634
634
  )
635
635
  return body, answers
636
636
 
637
- def get_body(self) -> ContentAST.Section:
637
+ def get_body(self) -> ca.Section:
638
638
  """Build question body (backward compatible interface)."""
639
639
  body, _ = self._get_body()
640
640
  return body
641
641
 
642
642
  def _get_explanation(self):
643
- explanation = ContentAST.Section()
643
+ explanation = ca.Section()
644
644
 
645
645
  explanation.add_element(
646
- ContentAST.Paragraph(
646
+ ca.Paragraph(
647
647
  [
648
648
  "The core idea to keep in mind with segmentation is that you should always check ",
649
649
  "the first two bits of the virtual address to see what segment it is in and then go from there."
@@ -654,7 +654,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
654
654
  )
655
655
 
656
656
  explanation.add_element(
657
- ContentAST.Paragraph(
657
+ ca.Paragraph(
658
658
  [
659
659
  f"In this problem our virtual address, "
660
660
  f"converted to binary and including padding, is 0b{self.virtual_address:0{self.virtual_bits}b}.",
@@ -667,7 +667,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
667
667
 
668
668
  if self.segment == "unallocated":
669
669
  explanation.add_element(
670
- ContentAST.Paragraph(
670
+ ca.Paragraph(
671
671
  [
672
672
  "Since this is the unallocated segment there are no possible valid translations, so we enter ***INVALID***."
673
673
  ]
@@ -675,7 +675,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
675
675
  )
676
676
  else:
677
677
  explanation.add_element(
678
- ContentAST.Paragraph(
678
+ ca.Paragraph(
679
679
  [
680
680
  f"Since we are in the {self.segment} segment, "
681
681
  f"we see from our table that our bounds are {self.bounds[self.segment]}. "
@@ -690,7 +690,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
690
690
  if not self.__within_bounds(self.segment, self.offset, self.bounds[self.segment]):
691
691
  # then we are outside of bounds
692
692
  explanation.add_element(
693
- ContentAST.Paragraph(
693
+ ca.Paragraph(
694
694
  [
695
695
  "We can therefore see that we are outside of bounds so we should put ***INVALID***.",
696
696
  "If we <i>were</i> requesting a valid memory location we could use the below steps to do so."
@@ -700,7 +700,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
700
700
  )
701
701
  else:
702
702
  explanation.add_element(
703
- ContentAST.Paragraph(
703
+ ca.Paragraph(
704
704
  [
705
705
  "We are therefore in bounds so we can calculate our physical address, as we do below."
706
706
  ]
@@ -708,7 +708,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
708
708
  )
709
709
 
710
710
  explanation.add_element(
711
- ContentAST.Paragraph(
711
+ ca.Paragraph(
712
712
  [
713
713
  "To find the physical address we use the formula:",
714
714
  "<code>physical_address = base(segment) + offset</code>",
@@ -720,14 +720,14 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
720
720
  )
721
721
 
722
722
  explanation.add_element(
723
- ContentAST.Paragraph(
723
+ ca.Paragraph(
724
724
  [
725
725
  "Lining this up for ease we can do this calculation as:"
726
726
  ]
727
727
  )
728
728
  )
729
729
  explanation.add_element(
730
- ContentAST.Code(
730
+ ca.Code(
731
731
  f" 0b{self.base[self.segment]:0{self.physical_bits}b}\n"
732
732
  f"<u>+ 0b{self.offset:0{self.physical_bits}b}</u>\n"
733
733
  f" 0b{self.physical_address:0{self.physical_bits}b}\n"
@@ -736,7 +736,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
736
736
 
737
737
  return explanation, []
738
738
 
739
- def get_explanation(self) -> ContentAST.Section:
739
+ def get_explanation(self) -> ca.Section:
740
740
  """Build question explanation (backward compatible interface)."""
741
741
  explanation, _ = self._get_explanation()
742
742
  return explanation
@@ -812,27 +812,27 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
812
812
 
813
813
  self.answers.update(
814
814
  {
815
- "answer__vpn": AnswerTypes.Binary(self.vpn, length=self.num_bits_vpn, label="VPN"),
816
- "answer__offset": AnswerTypes.Binary(self.offset, length=self.num_bits_offset, label="Offset"),
817
- "answer__pte": AnswerTypes.Binary(self.pte, length=(self.num_bits_pfn + 1), label="PTE"),
815
+ "answer__vpn": ca.AnswerTypes.Binary(self.vpn, length=self.num_bits_vpn, label="VPN"),
816
+ "answer__offset": ca.AnswerTypes.Binary(self.offset, length=self.num_bits_offset, label="Offset"),
817
+ "answer__pte": ca.AnswerTypes.Binary(self.pte, length=(self.num_bits_pfn + 1), label="PTE"),
818
818
  }
819
819
  )
820
820
 
821
821
  if self.is_valid:
822
822
  self.answers.update(
823
823
  {
824
- "answer__is_valid": AnswerTypes.String("VALID", label="VALID or INVALID?"),
825
- "answer__pfn": AnswerTypes.Binary(self.pfn, length=self.num_bits_pfn, label="PFN"),
826
- "answer__physical_address": AnswerTypes.Binary(self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
824
+ "answer__is_valid": ca.AnswerTypes.String("VALID", label="VALID or INVALID?"),
825
+ "answer__pfn": ca.AnswerTypes.Binary(self.pfn, length=self.num_bits_pfn, label="PFN"),
826
+ "answer__physical_address": ca.AnswerTypes.Binary(self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
827
827
  ),
828
828
  }
829
829
  )
830
830
  else:
831
831
  self.answers.update(
832
832
  {
833
- "answer__is_valid": AnswerTypes.String("INVALID", label="VALID or INVALID?"),
834
- "answer__pfn": AnswerTypes.String("INVALID", label="PFN"),
835
- "answer__physical_address": AnswerTypes.String("INVALID", label="Physical Address"),
833
+ "answer__is_valid": ca.AnswerTypes.String("INVALID", label="VALID or INVALID?"),
834
+ "answer__pfn": ca.AnswerTypes.String("INVALID", label="PFN"),
835
+ "answer__physical_address": ca.AnswerTypes.String("INVALID", label="Physical Address"),
836
836
  }
837
837
  )
838
838
 
@@ -847,10 +847,10 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
847
847
  self.answers["answer__physical_address"],
848
848
  ]
849
849
 
850
- body = ContentAST.Section()
850
+ body = ca.Section()
851
851
 
852
852
  body.add_element(
853
- ContentAST.Paragraph(
853
+ ca.Paragraph(
854
854
  [
855
855
  "Given the below information please calculate the equivalent physical address of the given virtual address, filling out all steps along the way.",
856
856
  "Remember, we typically have the MSB representing valid or invalid."
@@ -885,14 +885,14 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
885
885
  value_matrix.append(["...", "..."])
886
886
 
887
887
  body.add_element(
888
- ContentAST.Table(
888
+ ca.Table(
889
889
  headers=["VPN", "PTE"],
890
890
  data=value_matrix
891
891
  )
892
892
  )
893
893
 
894
894
  body.add_element(
895
- ContentAST.AnswerBlock([
895
+ ca.AnswerBlock([
896
896
  self.answers["answer__vpn"],
897
897
  self.answers["answer__offset"],
898
898
  self.answers["answer__pte"],
@@ -904,17 +904,17 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
904
904
 
905
905
  return body, answers
906
906
 
907
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
907
+ def get_body(self, *args, **kwargs) -> ca.Section:
908
908
  """Build question body (backward compatible interface)."""
909
909
  body, _ = self._get_body(*args, **kwargs)
910
910
  return body
911
911
 
912
912
  def _get_explanation(self, *args, **kwargs):
913
913
  """Build question explanation."""
914
- explanation = ContentAST.Section()
914
+ explanation = ca.Section()
915
915
 
916
916
  explanation.add_element(
917
- ContentAST.Paragraph(
917
+ ca.Paragraph(
918
918
  [
919
919
  "The core idea of Paging is we want to break the virtual address into the VPN and the offset. "
920
920
  "From here, we get the Page Table Entry corresponding to the VPN, and check the validity of the entry. "
@@ -924,7 +924,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
924
924
  )
925
925
 
926
926
  explanation.add_element(
927
- ContentAST.Paragraph(
927
+ ca.Paragraph(
928
928
  [
929
929
  "Don't forget to pad with the appropriate number of 0s (the appropriate number is the number of bits)!",
930
930
  ]
@@ -932,7 +932,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
932
932
  )
933
933
 
934
934
  explanation.add_element(
935
- ContentAST.Paragraph(
935
+ ca.Paragraph(
936
936
  [
937
937
  f"Virtual Address = VPN | offset",
938
938
  f"<tt>0b{self.virtual_address:0{self.num_bits_vpn + self.num_bits_offset}b}</tt> "
@@ -942,7 +942,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
942
942
  )
943
943
 
944
944
  explanation.add_element(
945
- ContentAST.Paragraph(
945
+ ca.Paragraph(
946
946
  [
947
947
  "We next use our VPN to index into our page table and find the corresponding entry."
948
948
  f"Our Page Table Entry is ",
@@ -954,7 +954,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
954
954
 
955
955
  if self.is_valid:
956
956
  explanation.add_element(
957
- ContentAST.Paragraph(
957
+ ca.Paragraph(
958
958
  [
959
959
  f"In our PTE we see that the first bit is **{self.pte // (2 ** self.num_bits_pfn)}** meaning that the translation is **VALID**"
960
960
  ]
@@ -962,7 +962,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
962
962
  )
963
963
  else:
964
964
  explanation.add_element(
965
- ContentAST.Paragraph(
965
+ ca.Paragraph(
966
966
  [
967
967
  f"In our PTE we see that the first bit is **{self.pte // (2 ** self.num_bits_pfn)}** meaning that the translation is **INVALID**.",
968
968
  "Therefore, we just write \"INVALID\" as our answer.",
@@ -973,7 +973,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
973
973
  )
974
974
 
975
975
  explanation.add_element(
976
- ContentAST.Paragraph(
976
+ ca.Paragraph(
977
977
  [
978
978
  "Next, we convert our PTE to our PFN by removing our metadata. "
979
979
  "In this case we're just removing the leading bit. We can do this by applying a binary mask.",
@@ -983,7 +983,7 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
983
983
  )
984
984
  )
985
985
  explanation.add_element(
986
- ContentAST.Equation(
986
+ ca.Equation(
987
987
  f"\\texttt{{{self.pfn:0{self.num_bits_pfn}b}}} "
988
988
  f"= \\texttt{{0b{self.pte:0{self.num_bits_pfn + 1}b}}} "
989
989
  f"\\& \\texttt{{0b{(2 ** self.num_bits_pfn) - 1:0{self.num_bits_pfn + 1}b}}}"
@@ -992,13 +992,13 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
992
992
 
993
993
  explanation.add_elements(
994
994
  [
995
- ContentAST.Paragraph(
995
+ ca.Paragraph(
996
996
  [
997
997
  "We then add combine our PFN and offset, "
998
998
  "Physical Address = PFN | offset",
999
999
  ]
1000
1000
  ),
1001
- ContentAST.Equation(
1001
+ ca.Equation(
1002
1002
  fr"{r'\mathbf{' if self.is_valid else ''}\mathtt{{0b{self.physical_address:0{self.num_bits_pfn + self.num_bits_offset}b}}}{r'}' if self.is_valid else ''} = \mathtt{{0b{self.pfn:0{self.num_bits_pfn}b}}} \mid \mathtt{{0b{self.offset:0{self.num_bits_offset}b}}}"
1003
1003
  )
1004
1004
  ]
@@ -1006,17 +1006,17 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
1006
1006
 
1007
1007
  explanation.add_elements(
1008
1008
  [
1009
- ContentAST.Paragraph(["Note: Strictly speaking, this calculation is:", ]),
1010
- ContentAST.Equation(
1009
+ ca.Paragraph(["Note: Strictly speaking, this calculation is:", ]),
1010
+ ca.Equation(
1011
1011
  fr"{r'\mathbf{' if self.is_valid else ''}\mathtt{{0b{self.physical_address:0{self.num_bits_pfn + self.num_bits_offset}b}}}{r'}' if self.is_valid else ''} = \mathtt{{0b{self.pfn:0{self.num_bits_pfn}b}{0:0{self.num_bits_offset}}}} + \mathtt{{0b{self.offset:0{self.num_bits_offset}b}}}"
1012
1012
  ),
1013
- ContentAST.Paragraph(["But that's a lot of extra 0s, so I'm splitting them up for succinctness"])
1013
+ ca.Paragraph(["But that's a lot of extra 0s, so I'm splitting them up for succinctness"])
1014
1014
  ]
1015
1015
  )
1016
1016
 
1017
1017
  return explanation, []
1018
1018
 
1019
- def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
1019
+ def get_explanation(self, *args, **kwargs) -> ca.Section:
1020
1020
  """Build question explanation (backward compatible interface)."""
1021
1021
  explanation, _ = self._get_explanation(*args, **kwargs)
1022
1022
  return explanation
@@ -1172,19 +1172,19 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1172
1172
 
1173
1173
  # Set up answers
1174
1174
  self.answers.update({
1175
- "answer__pdi": AnswerTypes.Binary(self.pdi, length=self.num_bits_pdi,
1175
+ "answer__pdi": ca.AnswerTypes.Binary(self.pdi, length=self.num_bits_pdi,
1176
1176
  label="PDI (Page Directory Index)"),
1177
- "answer__pti": AnswerTypes.Binary(self.pti, length=self.num_bits_pti,
1177
+ "answer__pti": ca.AnswerTypes.Binary(self.pti, length=self.num_bits_pti,
1178
1178
  label="PTI (Page Table Index)"),
1179
- "answer__offset": AnswerTypes.Binary(self.offset, length=self.num_bits_offset,
1179
+ "answer__offset": ca.AnswerTypes.Binary(self.offset, length=self.num_bits_offset,
1180
1180
  label="Offset"),
1181
- "answer__pd_entry": AnswerTypes.Binary(self.pd_entry, length=(self.num_bits_pfn + 1),
1181
+ "answer__pd_entry": ca.AnswerTypes.Binary(self.pd_entry, length=(self.num_bits_pfn + 1),
1182
1182
  label="PD Entry (from Page Directory)"),
1183
1183
  "answer__pt_number": (
1184
- AnswerTypes.Binary(self.page_table_number, length=self.num_bits_pfn,
1184
+ ca.AnswerTypes.Binary(self.page_table_number, length=self.num_bits_pfn,
1185
1185
  label="Page Table Number")
1186
1186
  if self.pd_valid
1187
- else AnswerTypes.String("INVALID", label="Page Table Number")
1187
+ else ca.AnswerTypes.String("INVALID", label="Page Table Number")
1188
1188
  ),
1189
1189
  })
1190
1190
 
@@ -1192,29 +1192,29 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1192
1192
  # (regardless of whether that PTE is valid or invalid)
1193
1193
  if self.pd_valid:
1194
1194
  self.answers.update({
1195
- "answer__pte": AnswerTypes.Binary(self.pte, length=(self.num_bits_pfn + 1),
1195
+ "answer__pte": ca.AnswerTypes.Binary(self.pte, length=(self.num_bits_pfn + 1),
1196
1196
  label="PTE (from Page Table)"),
1197
1197
  })
1198
1198
  else:
1199
1199
  # If PD is invalid, student can't look up the page table
1200
1200
  # Accept both "INVALID" (for consistency) and "N/A" (for accuracy)
1201
1201
  self.answers.update({
1202
- "answer__pte": AnswerTypes.String(["INVALID", "N/A"], label="PTE (from Page Table)"),
1202
+ "answer__pte": ca.AnswerTypes.String(["INVALID", "N/A"], label="PTE (from Page Table)"),
1203
1203
  })
1204
1204
 
1205
1205
  # Validity, PFN, and Physical Address depend on BOTH levels being valid
1206
1206
  if self.pd_valid and self.pt_valid:
1207
1207
  self.answers.update({
1208
- "answer__is_valid": AnswerTypes.String("VALID", label="VALID or INVALID?"),
1209
- "answer__pfn": AnswerTypes.Binary(self.pfn, length=self.num_bits_pfn, label="PFN"),
1210
- "answer__physical_address": AnswerTypes.Binary(self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
1208
+ "answer__is_valid": ca.AnswerTypes.String("VALID", label="VALID or INVALID?"),
1209
+ "answer__pfn": ca.AnswerTypes.Binary(self.pfn, length=self.num_bits_pfn, label="PFN"),
1210
+ "answer__physical_address": ca.AnswerTypes.Binary(self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
1211
1211
  ),
1212
1212
  })
1213
1213
  else:
1214
1214
  self.answers.update({
1215
- "answer__is_valid": AnswerTypes.String("INVALID", label="VALID or INVALID?"),
1216
- "answer__pfn": AnswerTypes.String("INVALID", label="PFN"),
1217
- "answer__physical_address": AnswerTypes.String("INVALID", label="Physical Address"),
1215
+ "answer__is_valid": ca.AnswerTypes.String("INVALID", label="VALID or INVALID?"),
1216
+ "answer__pfn": ca.AnswerTypes.String("INVALID", label="PFN"),
1217
+ "answer__physical_address": ca.AnswerTypes.String("INVALID", label="Physical Address"),
1218
1218
  })
1219
1219
 
1220
1220
  def _get_body(self, *args, **kwargs):
@@ -1231,10 +1231,10 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1231
1231
  self.answers["answer__physical_address"],
1232
1232
  ]
1233
1233
 
1234
- body = ContentAST.Section()
1234
+ body = ca.Section()
1235
1235
 
1236
1236
  body.add_element(
1237
- ContentAST.Paragraph([
1237
+ ca.Paragraph([
1238
1238
  "Given the below information please calculate the equivalent physical address of the given virtual address, filling out all steps along the way.",
1239
1239
  "This problem uses **two-level (hierarchical) paging**.",
1240
1240
  "Remember, we typically have the MSB representing valid or invalid."
@@ -1267,19 +1267,19 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1267
1267
 
1268
1268
  # Use a simple text paragraph - the bold will come from markdown conversion
1269
1269
  body.add_element(
1270
- ContentAST.Paragraph([
1270
+ ca.Paragraph([
1271
1271
  "**Page Directory:**"
1272
1272
  ])
1273
1273
  )
1274
1274
  body.add_element(
1275
- ContentAST.Table(
1275
+ ca.Table(
1276
1276
  headers=["PDI", "PD Entry"],
1277
1277
  data=pd_matrix
1278
1278
  )
1279
1279
  )
1280
1280
 
1281
1281
  # Page Tables - use TableGroup for side-by-side display
1282
- table_group = ContentAST.TableGroup()
1282
+ table_group = ca.TableGroup()
1283
1283
 
1284
1284
  for pt_num in sorted(self.page_tables.keys()):
1285
1285
  pt_matrix = []
@@ -1323,14 +1323,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1323
1323
 
1324
1324
  table_group.add_table(
1325
1325
  label=f"PTC 0b{pt_num:0{self.num_bits_pfn}b}:",
1326
- table=ContentAST.Table(headers=["PTI", "PTE"], data=pt_matrix)
1326
+ table=ca.Table(headers=["PTI", "PTE"], data=pt_matrix)
1327
1327
  )
1328
1328
 
1329
1329
  body.add_element(table_group)
1330
1330
 
1331
1331
  # Answer block
1332
1332
  body.add_element(
1333
- ContentAST.AnswerBlock([
1333
+ ca.AnswerBlock([
1334
1334
  self.answers["answer__pdi"],
1335
1335
  self.answers["answer__pti"],
1336
1336
  self.answers["answer__offset"],
@@ -1345,31 +1345,31 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1345
1345
 
1346
1346
  return body, answers
1347
1347
 
1348
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
1348
+ def get_body(self, *args, **kwargs) -> ca.Section:
1349
1349
  """Build question body (backward compatible interface)."""
1350
1350
  body, _ = self._get_body(*args, **kwargs)
1351
1351
  return body
1352
1352
 
1353
1353
  def _get_explanation(self, *args, **kwargs):
1354
1354
  """Build question explanation."""
1355
- explanation = ContentAST.Section()
1355
+ explanation = ca.Section()
1356
1356
 
1357
1357
  explanation.add_element(
1358
- ContentAST.Paragraph([
1358
+ ca.Paragraph([
1359
1359
  "Two-level paging requires two lookups: first in the Page Directory, then in a Page Table.",
1360
1360
  "The virtual address is split into three parts: PDI | PTI | Offset."
1361
1361
  ])
1362
1362
  )
1363
1363
 
1364
1364
  explanation.add_element(
1365
- ContentAST.Paragraph([
1365
+ ca.Paragraph([
1366
1366
  "Don't forget to pad with the appropriate number of 0s!"
1367
1367
  ])
1368
1368
  )
1369
1369
 
1370
1370
  # Step 1: Extract PDI, PTI, Offset
1371
1371
  explanation.add_element(
1372
- ContentAST.Paragraph([
1372
+ ca.Paragraph([
1373
1373
  f"**Step 1: Extract components from Virtual Address**",
1374
1374
  f"Virtual Address = PDI | PTI | Offset",
1375
1375
  f"<tt>0b{self.virtual_address:0{self.num_bits_vpn + self.num_bits_offset}b}</tt> = "
@@ -1381,7 +1381,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1381
1381
 
1382
1382
  # Step 2: Look up PD Entry
1383
1383
  explanation.add_element(
1384
- ContentAST.Paragraph([
1384
+ ca.Paragraph([
1385
1385
  f"**Step 2: Look up Page Directory Entry**",
1386
1386
  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>"
1387
1387
  ])
@@ -1390,7 +1390,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1390
1390
  # Step 3: Check PD validity
1391
1391
  pd_valid_bit = self.pd_entry // (2 ** self.num_bits_pfn)
1392
1392
  explanation.add_element(
1393
- ContentAST.Paragraph([
1393
+ ca.Paragraph([
1394
1394
  f"**Step 3: Check Page Directory Entry validity**",
1395
1395
  f"The MSB (valid bit) is **{pd_valid_bit}**, so this PD Entry is **{'VALID' if self.pd_valid else 'INVALID'}**."
1396
1396
  ])
@@ -1398,7 +1398,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1398
1398
 
1399
1399
  if not self.pd_valid:
1400
1400
  explanation.add_element(
1401
- ContentAST.Paragraph([
1401
+ ca.Paragraph([
1402
1402
  "Since the Page Directory Entry is invalid, the translation fails here.",
1403
1403
  "We write **INVALID** for all remaining fields.",
1404
1404
  "If it were valid, we would continue with the steps below.",
@@ -1408,14 +1408,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1408
1408
 
1409
1409
  # Step 4: Extract PT number (if PD valid)
1410
1410
  explanation.add_element(
1411
- ContentAST.Paragraph([
1411
+ ca.Paragraph([
1412
1412
  f"**Step 4: Extract Page Table Number**",
1413
1413
  "We remove the valid bit from the PD Entry to get the Page Table Number:"
1414
1414
  ])
1415
1415
  )
1416
1416
 
1417
1417
  explanation.add_element(
1418
- ContentAST.Equation(
1418
+ ca.Equation(
1419
1419
  f"\\texttt{{{self.page_table_number:0{self.num_bits_pfn}b}}} = "
1420
1420
  f"\\texttt{{0b{self.pd_entry:0{self.num_bits_pfn + 1}b}}} \\& "
1421
1421
  f"\\texttt{{0b{(2 ** self.num_bits_pfn) - 1:0{self.num_bits_pfn + 1}b}}}"
@@ -1424,14 +1424,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1424
1424
 
1425
1425
  if self.pd_valid:
1426
1426
  explanation.add_element(
1427
- ContentAST.Paragraph([
1427
+ ca.Paragraph([
1428
1428
  f"This tells us to use **Page Table #{self.page_table_number}**."
1429
1429
  ])
1430
1430
  )
1431
1431
 
1432
1432
  # Step 5: Look up PTE
1433
1433
  explanation.add_element(
1434
- ContentAST.Paragraph([
1434
+ ca.Paragraph([
1435
1435
  f"**Step 5: Look up Page Table Entry**",
1436
1436
  f"Using PTI = <tt>0b{self.pti:0{self.num_bits_pti}b}</tt> in Page Table #{self.page_table_number}, "
1437
1437
  f"we find PTE = <tt>0b{self.pte:0{self.num_bits_pfn + 1}b}</tt>"
@@ -1441,7 +1441,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1441
1441
  # Step 6: Check PT validity
1442
1442
  pt_valid_bit = self.pte // (2 ** self.num_bits_pfn)
1443
1443
  explanation.add_element(
1444
- ContentAST.Paragraph([
1444
+ ca.Paragraph([
1445
1445
  f"**Step 6: Check Page Table Entry validity**",
1446
1446
  f"The MSB (valid bit) is **{pt_valid_bit}**, so this PTE is **{'VALID' if self.pt_valid else 'INVALID'}**."
1447
1447
  ])
@@ -1449,7 +1449,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1449
1449
 
1450
1450
  if not self.pt_valid:
1451
1451
  explanation.add_element(
1452
- ContentAST.Paragraph([
1452
+ ca.Paragraph([
1453
1453
  "Since the Page Table Entry is invalid, the translation fails.",
1454
1454
  "We write **INVALID** for PFN and Physical Address.",
1455
1455
  "If it were valid, we would continue with the steps below.",
@@ -1459,14 +1459,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1459
1459
 
1460
1460
  # Step 7: Extract PFN
1461
1461
  explanation.add_element(
1462
- ContentAST.Paragraph([
1462
+ ca.Paragraph([
1463
1463
  f"**Step 7: Extract PFN**",
1464
1464
  "We remove the valid bit from the PTE to get the PFN:"
1465
1465
  ])
1466
1466
  )
1467
1467
 
1468
1468
  explanation.add_element(
1469
- ContentAST.Equation(
1469
+ ca.Equation(
1470
1470
  f"\\texttt{{{self.pfn:0{self.num_bits_pfn}b}}} = "
1471
1471
  f"\\texttt{{0b{self.pte:0{self.num_bits_pfn + 1}b}}} \\& "
1472
1472
  f"\\texttt{{0b{(2 ** self.num_bits_pfn) - 1:0{self.num_bits_pfn + 1}b}}}"
@@ -1475,14 +1475,14 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1475
1475
 
1476
1476
  # Step 8: Construct physical address
1477
1477
  explanation.add_element(
1478
- ContentAST.Paragraph([
1478
+ ca.Paragraph([
1479
1479
  f"**Step 8: Construct Physical Address**",
1480
1480
  "Physical Address = PFN | Offset"
1481
1481
  ])
1482
1482
  )
1483
1483
 
1484
1484
  explanation.add_element(
1485
- ContentAST.Equation(
1485
+ ca.Equation(
1486
1486
  fr"{r'\mathbf{' if self.is_valid else ''}\mathtt{{0b{self.physical_address:0{self.num_bits_pfn + self.num_bits_offset}b}}}{r'}' if self.is_valid else ''} = "
1487
1487
  f"\\mathtt{{0b{self.pfn:0{self.num_bits_pfn}b}}} \\mid "
1488
1488
  f"\\mathtt{{0b{self.offset:0{self.num_bits_offset}b}}}"
@@ -1491,7 +1491,7 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1491
1491
 
1492
1492
  return explanation, []
1493
1493
 
1494
- def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
1494
+ def get_explanation(self, *args, **kwargs) -> ca.Section:
1495
1495
  """Build question explanation (backward compatible interface)."""
1496
1496
  explanation, _ = self._get_explanation(*args, **kwargs)
1497
1497
  return explanation