QuizGenerator 0.5.1__py3-none-any.whl → 0.6.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 (29) hide show
  1. QuizGenerator/contentast.py +1056 -1231
  2. QuizGenerator/generate.py +174 -2
  3. QuizGenerator/misc.py +0 -6
  4. QuizGenerator/mixins.py +7 -8
  5. QuizGenerator/premade_questions/basic.py +3 -3
  6. QuizGenerator/premade_questions/cst334/languages.py +45 -51
  7. QuizGenerator/premade_questions/cst334/math_questions.py +9 -10
  8. QuizGenerator/premade_questions/cst334/memory_questions.py +39 -56
  9. QuizGenerator/premade_questions/cst334/persistence_questions.py +12 -27
  10. QuizGenerator/premade_questions/cst334/process.py +11 -22
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +11 -11
  12. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +7 -7
  13. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +6 -6
  14. QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
  15. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +15 -19
  16. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -442
  17. QuizGenerator/premade_questions/cst463/models/attention.py +7 -8
  18. QuizGenerator/premade_questions/cst463/models/cnns.py +6 -7
  19. QuizGenerator/premade_questions/cst463/models/rnns.py +6 -6
  20. QuizGenerator/premade_questions/cst463/models/text.py +7 -8
  21. QuizGenerator/premade_questions/cst463/models/weight_counting.py +5 -9
  22. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +22 -22
  23. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +25 -25
  24. QuizGenerator/question.py +13 -14
  25. {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/METADATA +1 -1
  26. {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/RECORD +29 -29
  27. {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/WHEEL +0 -0
  28. {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/entry_points.txt +0 -0
  29. {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -9,8 +9,8 @@ import logging
9
9
  import math
10
10
  from typing import List, Optional
11
11
 
12
- from QuizGenerator.contentast import ContentAST
13
- from QuizGenerator.question import Question, Answer, QuestionRegistry, RegenerableChoiceMixin
12
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
13
+ from QuizGenerator.question import Question, QuestionRegistry, RegenerableChoiceMixin
14
14
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
15
15
 
16
16
  log = logging.getLogger(__name__)
@@ -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, 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")
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")
46
46
  }
47
47
 
48
48
  # Select what kind of question we are going to be
@@ -233,6 +233,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
233
233
  number_of_hits = 0
234
234
  for (request_number, request) in enumerate(self.requests):
235
235
  was_hit, evicted, cache_state = self.cache.query_cache(request, request_number)
236
+ log.debug(f"cache_state: \"{cache_state}\"")
236
237
  if was_hit:
237
238
  number_of_hits += 1
238
239
  self.request_results[request_number] = {
@@ -244,24 +245,17 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
244
245
 
245
246
  self.answers.update(
246
247
  {
247
- f"answer__hit-{request_number}": Answer.string(
248
- f"answer__hit-{request_number}", ('hit' if was_hit else 'miss')
249
- ),
250
- f"answer__evicted-{request_number}": Answer.string(
251
- f"answer__evicted-{request_number}", ('-' if evicted is None else f"{evicted}")
252
- ),
253
- f"answer__cache_state-{request_number}": Answer.list_value(
254
- f"answer__cache_state-{request_number}", copy.copy(cache_state)
255
- ),
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),
256
251
  }
257
252
  )
258
253
 
259
254
  self.hit_rate = 100 * number_of_hits / (self.num_requests)
260
255
  self.answers.update(
261
256
  {
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.",
257
+ "answer__hit_rate": AnswerTypes.Float(self.hit_rate,
258
+ label=f"Hit rate, excluding non-capacity misses",
265
259
  unit="%"
266
260
  )
267
261
  }
@@ -399,13 +393,12 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
399
393
  self.virtual_address = self.rng.randint(1, int(self.bounds / self.PROBABILITY_OF_VALID))
400
394
 
401
395
  if self.virtual_address < self.bounds:
402
- self.answers["answer"] = Answer.binary_hex(
403
- "answer__physical_address",
396
+ self.answers["answer"] = AnswerTypes.Hex(
404
397
  self.base + self.virtual_address,
405
398
  length=math.ceil(math.log2(self.base + self.virtual_address))
406
399
  )
407
400
  else:
408
- self.answers["answer"] = Answer.string("answer__physical_address", "INVALID")
401
+ self.answers["answer"] = AnswerTypes.String("INVALID")
409
402
 
410
403
  def _get_body(self):
411
404
  """Build question body and collect answers."""
@@ -586,21 +579,15 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
586
579
 
587
580
  # Set answers based on whether it's in bounds or not
588
581
  if self.__within_bounds(self.segment, self.offset, self.bounds[self.segment]):
589
- self.answers["answer__physical_address"] = Answer.binary_hex(
590
- "answer__physical_address",
582
+ self.answers["answer__physical_address"] = AnswerTypes.Binary(
591
583
  self.physical_address,
592
584
  length=self.physical_bits,
593
585
  label="Physical Address"
594
586
  )
595
587
  else:
596
- self.answers["answer__physical_address"] = Answer.string(
597
- "answer__physical_address",
598
- "INVALID",
599
- label="Physical Address"
600
- )
588
+ self.answers["answer__physical_address"] = AnswerTypes.String("INVALID", label="Physical Address")
601
589
 
602
- self.answers["answer__segment"] = Answer.string("answer__segment", self.segment,
603
- label="Segment name")
590
+ self.answers["answer__segment"] = AnswerTypes.String(self.segment, label="Segment name")
604
591
 
605
592
  def _get_body(self):
606
593
  """Build question body and collect answers."""
@@ -825,29 +812,27 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
825
812
 
826
813
  self.answers.update(
827
814
  {
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"),
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"),
831
818
  }
832
819
  )
833
820
 
834
821
  if self.is_valid:
835
822
  self.answers.update(
836
823
  {
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"),
839
- "answer__physical_address": Answer.binary_hex(
840
- "answer__physical_address", self.physical_address,
841
- length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
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"
842
827
  ),
843
828
  }
844
829
  )
845
830
  else:
846
831
  self.answers.update(
847
832
  {
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"),
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"),
851
836
  }
852
837
  )
853
838
 
@@ -1187,19 +1172,19 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1187
1172
 
1188
1173
  # Set up answers
1189
1174
  self.answers.update({
1190
- "answer__pdi": Answer.binary_hex("answer__pdi", self.pdi, length=self.num_bits_pdi,
1175
+ "answer__pdi": AnswerTypes.Binary(self.pdi, length=self.num_bits_pdi,
1191
1176
  label="PDI (Page Directory Index)"),
1192
- "answer__pti": Answer.binary_hex("answer__pti", self.pti, length=self.num_bits_pti,
1177
+ "answer__pti": AnswerTypes.Binary(self.pti, length=self.num_bits_pti,
1193
1178
  label="PTI (Page Table Index)"),
1194
- "answer__offset": Answer.binary_hex("answer__offset", self.offset, length=self.num_bits_offset,
1179
+ "answer__offset": AnswerTypes.Binary(self.offset, length=self.num_bits_offset,
1195
1180
  label="Offset"),
1196
- "answer__pd_entry": Answer.binary_hex("answer__pd_entry", self.pd_entry, length=(self.num_bits_pfn + 1),
1181
+ "answer__pd_entry": AnswerTypes.Binary(self.pd_entry, length=(self.num_bits_pfn + 1),
1197
1182
  label="PD Entry (from Page Directory)"),
1198
1183
  "answer__pt_number": (
1199
- Answer.binary_hex("answer__pt_number", self.page_table_number, length=self.num_bits_pfn,
1184
+ AnswerTypes.Binary(self.page_table_number, length=self.num_bits_pfn,
1200
1185
  label="Page Table Number")
1201
1186
  if self.pd_valid
1202
- else Answer.string("answer__pt_number", "INVALID", label="Page Table Number")
1187
+ else AnswerTypes.String("INVALID", label="Page Table Number")
1203
1188
  ),
1204
1189
  })
1205
1190
 
@@ -1207,31 +1192,29 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
1207
1192
  # (regardless of whether that PTE is valid or invalid)
1208
1193
  if self.pd_valid:
1209
1194
  self.answers.update({
1210
- "answer__pte": Answer.binary_hex("answer__pte", self.pte, length=(self.num_bits_pfn + 1),
1195
+ "answer__pte": AnswerTypes.Binary(self.pte, length=(self.num_bits_pfn + 1),
1211
1196
  label="PTE (from Page Table)"),
1212
1197
  })
1213
1198
  else:
1214
1199
  # If PD is invalid, student can't look up the page table
1215
1200
  # Accept both "INVALID" (for consistency) and "N/A" (for accuracy)
1216
1201
  self.answers.update({
1217
- "answer__pte": Answer.string("answer__pte", ["INVALID", "N/A"], label="PTE (from Page Table)"),
1202
+ "answer__pte": AnswerTypes.String(["INVALID", "N/A"], label="PTE (from Page Table)"),
1218
1203
  })
1219
1204
 
1220
1205
  # Validity, PFN, and Physical Address depend on BOTH levels being valid
1221
1206
  if self.pd_valid and self.pt_valid:
1222
1207
  self.answers.update({
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"),
1225
- "answer__physical_address": Answer.binary_hex(
1226
- "answer__physical_address", self.physical_address,
1227
- length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
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"
1228
1211
  ),
1229
1212
  })
1230
1213
  else:
1231
1214
  self.answers.update({
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"),
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"),
1235
1218
  })
1236
1219
 
1237
1220
  def _get_body(self, *args, **kwargs):
@@ -5,8 +5,8 @@ import abc
5
5
  import difflib
6
6
  import logging
7
7
 
8
- from QuizGenerator.question import Question, Answer, QuestionRegistry
9
- from QuizGenerator.contentast import ContentAST
8
+ from QuizGenerator.question import Question, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
10
10
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
11
11
 
12
12
  log = logging.getLogger(__name__)
@@ -36,22 +36,10 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
36
36
  self.disk_access_delay = self.access_delay * self.number_of_reads + self.transfer_delay
37
37
 
38
38
  self.answers.update({
39
- "answer__rotational_delay": Answer.float_value(
40
- "answer__rotational_delay",
41
- self.rotational_delay
42
- ),
43
- "answer__access_delay": Answer.float_value(
44
- "answer__access_delay",
45
- self.access_delay
46
- ),
47
- "answer__transfer_delay": Answer.float_value(
48
- "answer__transfer_delay",
49
- self.transfer_delay
50
- ),
51
- "answer__disk_access_delay": Answer.float_value(
52
- "answer__disk_access_delay",
53
- self.disk_access_delay
54
- ),
39
+ "answer__rotational_delay" : AnswerTypes.Float(self.rotational_delay),
40
+ "answer__access_delay" : AnswerTypes.Float(self.access_delay),
41
+ "answer__transfer_delay" : AnswerTypes.Float(self.transfer_delay),
42
+ "answer__disk_access_delay" : AnswerTypes.Float(self.disk_access_delay),
55
43
  })
56
44
 
57
45
  def _get_body(self, *args, **kwargs):
@@ -190,10 +178,10 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
190
178
  self.inode_index_in_block = int(self.inode_address_in_block / self.inode_size)
191
179
 
192
180
  self.answers.update({
193
- "answer__inode_address": Answer.integer("answer__inode_address", self.inode_address),
194
- "answer__inode_block": Answer.integer("answer__inode_block", self.inode_block),
195
- "answer__inode_address_in_block": Answer.integer("answer__inode_address_in_block", self.inode_address_in_block),
196
- "answer__inode_index_in_block": Answer.integer("answer__inode_index_in_block", self.inode_index_in_block),
181
+ "answer__inode_address": AnswerTypes.Int(self.inode_address),
182
+ "answer__inode_block": AnswerTypes.Int(self.inode_block),
183
+ "answer__inode_address_in_block": AnswerTypes.Int(self.inode_address_in_block),
184
+ "answer__inode_index_in_block": AnswerTypes.Int(self.inode_index_in_block),
197
185
  })
198
186
 
199
187
  def _get_body(self):
@@ -334,7 +322,7 @@ class VSFS_states(IOQuestion):
334
322
 
335
323
  def __init__(self, *args, **kwargs):
336
324
  super().__init__(*args, **kwargs)
337
- self.answer_kind = Answer.AnswerKind.MULTIPLE_DROPDOWN
325
+ self.answer_kind = ContentAST.Answer.CanvasAnswerKind.MULTIPLE_DROPDOWN
338
326
 
339
327
  self.num_steps = kwargs.get("num_steps", 10)
340
328
 
@@ -356,11 +344,8 @@ class VSFS_states(IOQuestion):
356
344
  ))
357
345
  self.rng.shuffle(wrong_answers)
358
346
 
359
- self.answers["answer__cmd"] = Answer(
360
- "answer__cmd",
347
+ self.answers["answer__cmd"] = ContentAST.Answer.dropdown(
361
348
  f"{operations[-1]['cmd']}",
362
- kind=Answer.AnswerKind.MULTIPLE_DROPDOWN,
363
- correct=True,
364
349
  baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']])),
365
350
  label="Command"
366
351
  )
@@ -13,8 +13,8 @@ from typing import List
13
13
 
14
14
  import matplotlib.pyplot as plt
15
15
 
16
- from QuizGenerator.contentast import ContentAST
17
- from QuizGenerator.question import Question, Answer, QuestionRegistry, RegenerableChoiceMixin
16
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
17
+ from QuizGenerator.question import Question, QuestionRegistry, RegenerableChoiceMixin
18
18
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
19
19
 
20
20
  log = logging.getLogger(__name__)
@@ -356,23 +356,15 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
356
356
 
357
357
  for job_id in sorted(self.job_stats.keys()):
358
358
  self.answers.update({
359
- f"answer__response_time_job{job_id}": Answer.auto_float(
360
- f"answer__response_time_job{job_id}",
361
- self.job_stats[job_id]["Response"]
362
- ),
363
- f"answer__turnaround_time_job{job_id}": Answer.auto_float(
364
- f"answer__turnaround_time_job{job_id}",
365
- self.job_stats[job_id]["TAT"]
366
- ),
359
+ f"answer__response_time_job{job_id}": AnswerTypes.Float(self.job_stats[job_id]["Response"]),
360
+ f"answer__turnaround_time_job{job_id}": AnswerTypes.Float(self.job_stats[job_id]["TAT"]),
367
361
  })
368
362
  self.answers.update({
369
- "answer__average_response_time": Answer.auto_float(
370
- "answer__average_response_time",
363
+ "answer__average_response_time": AnswerTypes.Float(
371
364
  sum([job.response_time for job in jobs]) / len(jobs),
372
365
  label="Overall average response time"
373
366
  ),
374
- "answer__average_turnaround_time": Answer.auto_float(
375
- "answer__average_turnaround_time",
367
+ "answer__average_turnaround_time": AnswerTypes.Float(
376
368
  sum([job.turnaround_time for job in jobs]) / len(jobs),
377
369
  label="Overall average TAT"
378
370
  )
@@ -388,7 +380,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
388
380
  Tuple of (body_ast, answers_list)
389
381
  """
390
382
  from typing import List
391
- answers: List[Answer] = []
383
+ answers: List[ContentAST.Answer] = []
392
384
 
393
385
  # Create table data for scheduling results
394
386
  table_rows = []
@@ -427,7 +419,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
427
419
  )
428
420
 
429
421
  instructions = ContentAST.OnlyHtml([ContentAST.Paragraph([
430
- f"Please format answer as fractions, mixed numbers, or numbers rounded to a maximum of {Answer.DEFAULT_ROUNDING_DIGITS} digits after the decimal. "
422
+ f"Please format answer as fractions, mixed numbers, or numbers rounded to a maximum of {ContentAST.Answer.DEFAULT_ROUNDING_DIGITS} digits after the decimal. "
431
423
  "Examples of appropriately formatted answers would be `0`, `3/2`, `1 1/3`, `1.6667`, and `1.25`. "
432
424
  "Note that answers that can be rounded to whole numbers should be, rather than being left in fractional form."
433
425
  ])])
@@ -915,16 +907,13 @@ class MLFQQuestion(ProcessQuestion, TableQuestionMixin, BodyTemplatesMixin):
915
907
 
916
908
  for job_id in sorted(self.job_stats.keys()):
917
909
  self.answers.update({
918
- f"answer__turnaround_time_job{job_id}": Answer.auto_float(
919
- f"answer__turnaround_time_job{job_id}",
920
- self.job_stats[job_id]["TAT"]
921
- )
910
+ f"answer__turnaround_time_job{job_id}": AnswerTypes.Float(self.job_stats[job_id]["TAT"])
922
911
  })
923
912
 
924
913
  return self.is_interesting()
925
914
 
926
915
  def _get_body(self, *args, **kwargs):
927
- answers: List[Answer] = []
916
+ answers: List[ContentAST.Answer] = []
928
917
 
929
918
  queue_rows = []
930
919
  for i in reversed(range(self.num_queues)):
@@ -964,7 +953,7 @@ class MLFQQuestion(ProcessQuestion, TableQuestionMixin, BodyTemplatesMixin):
964
953
 
965
954
  instructions = (
966
955
  f"Compute the turnaround time (TAT) for each job. "
967
- f"Round to at most {Answer.DEFAULT_ROUNDING_DIGITS} digits after the decimal."
956
+ f"Round to at most {ContentAST.Answer.DEFAULT_ROUNDING_DIGITS} digits after the decimal."
968
957
  )
969
958
 
970
959
  body = ContentAST.Section()
@@ -5,8 +5,8 @@ import logging
5
5
  from typing import List, Tuple
6
6
  import sympy as sp
7
7
 
8
- from QuizGenerator.contentast import ContentAST
9
- from QuizGenerator.question import Question, Answer, QuestionRegistry
8
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
9
+ from QuizGenerator.question import Question, QuestionRegistry
10
10
  from .misc import generate_function, format_vector
11
11
 
12
12
  log = logging.getLogger(__name__)
@@ -57,7 +57,7 @@ class DerivativeQuestion(Question, abc.ABC):
57
57
  # Use auto_float for Canvas compatibility with integers and decimals
58
58
  # Label includes the partial derivative notation
59
59
  label = f"∂f/∂x_{i} at ({eval_point_str})"
60
- self.answers[answer_key] = Answer.auto_float(answer_key, gradient_value, label=label)
60
+ self.answers[answer_key] = AnswerTypes.Float(gradient_value, label=label)
61
61
 
62
62
  def _create_gradient_vector_answer(self) -> None:
63
63
  """Create a single gradient vector answer for PDF format."""
@@ -75,9 +75,9 @@ class DerivativeQuestion(Question, abc.ABC):
75
75
 
76
76
  # Format as vector for display using consistent formatting
77
77
  vector_str = format_vector(gradient_values)
78
- self.answers["gradient_vector"] = Answer.string("gradient_vector", vector_str, pdf_only=True)
78
+ self.answers["gradient_vector"] = AnswerTypes.String(vector_str, pdf_only=True)
79
79
 
80
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
80
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
81
81
  """Build question body and collect answers."""
82
82
  body = ContentAST.Section()
83
83
  answers = []
@@ -131,7 +131,7 @@ class DerivativeQuestion(Question, abc.ABC):
131
131
  body, _ = self._get_body(**kwargs)
132
132
  return body
133
133
 
134
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
134
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
135
135
  """Build question explanation."""
136
136
  explanation = ContentAST.Section()
137
137
 
@@ -162,14 +162,14 @@ class DerivativeQuestion(Question, abc.ABC):
162
162
  partial_expr = self.gradient_function[i]
163
163
  partial_value = partial_expr.subs(subs_map)
164
164
 
165
- # Use Answer.accepted_strings for clean numerical formatting
165
+ # Use ContentAST.Answer.accepted_strings for clean numerical formatting
166
166
  try:
167
167
  numerical_value = float(partial_value)
168
168
  except (TypeError, ValueError):
169
169
  numerical_value = float(partial_value.evalf())
170
170
 
171
171
  # Get clean string representation
172
- clean_value = sorted(Answer.accepted_strings(numerical_value), key=lambda s: len(s))[0]
172
+ clean_value = sorted(ContentAST.Answer.accepted_strings(numerical_value), key=lambda s: len(s))[0]
173
173
 
174
174
  explanation.add_element(
175
175
  ContentAST.Paragraph([
@@ -284,7 +284,7 @@ class DerivativeChain(DerivativeQuestion):
284
284
  f = sp.Function('f')
285
285
  self.equation = sp.Eq(f(*self.variables), self.function)
286
286
 
287
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
287
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
288
288
  """Build question explanation."""
289
289
  explanation = ContentAST.Section()
290
290
 
@@ -369,14 +369,14 @@ class DerivativeChain(DerivativeQuestion):
369
369
  partial_expr = self.gradient_function[i]
370
370
  partial_value = partial_expr.subs(subs_map)
371
371
 
372
- # Use Answer.accepted_strings for clean numerical formatting
372
+ # Use ContentAST.Answer.accepted_strings for clean numerical formatting
373
373
  try:
374
374
  numerical_value = float(partial_value)
375
375
  except (TypeError, ValueError):
376
376
  numerical_value = float(partial_value.evalf())
377
377
 
378
378
  # Get clean string representation
379
- clean_value = sorted(Answer.accepted_strings(numerical_value), key=lambda s: len(s))[0]
379
+ clean_value = sorted(ContentAST.Answer.accepted_strings(numerical_value), key=lambda s: len(s))[0]
380
380
 
381
381
  explanation.add_element(
382
382
  ContentAST.Paragraph([
@@ -6,8 +6,8 @@ import math
6
6
  from typing import List, Tuple, Callable, Union, Any
7
7
  import sympy as sp
8
8
 
9
- from QuizGenerator.contentast import ContentAST
10
- from QuizGenerator.question import Question, Answer, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
10
+ from QuizGenerator.question import Question, QuestionRegistry
11
11
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
12
12
 
13
13
  from .misc import generate_function, format_vector
@@ -98,17 +98,17 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
98
98
 
99
99
  # Location answer
100
100
  location_key = f"answer__location_{step}"
101
- self.answers[location_key] = Answer.vector_value(location_key, list(result['location']), label=f"Location at step {step}")
101
+ self.answers[location_key] = AnswerTypes.Vector(list(result['location']), label=f"Location at step {step}")
102
102
 
103
103
  # Gradient answer
104
104
  gradient_key = f"answer__gradient_{step}"
105
- self.answers[gradient_key] = Answer.vector_value(gradient_key, list(result['gradient']), label=f"Gradient at step {step}")
105
+ self.answers[gradient_key] = AnswerTypes.Vector(list(result['gradient']), label=f"Gradient at step {step}")
106
106
 
107
107
  # Update answer
108
108
  update_key = f"answer__update_{step}"
109
- self.answers[update_key] = Answer.vector_value(update_key, list(result['update']), label=f"Update at step {step}")
109
+ self.answers[update_key] = AnswerTypes.Vector(list(result['update']), label=f"Update at step {step}")
110
110
 
111
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
111
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
112
112
  """Build question body and collect answers."""
113
113
  body = ContentAST.Section()
114
114
  answers = []
@@ -179,7 +179,7 @@ class GradientDescentWalkthrough(GradientDescentQuestion, TableQuestionMixin, Bo
179
179
  body, _ = self._get_body(**kwargs)
180
180
  return body
181
181
 
182
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
182
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
183
183
  """Build question explanation."""
184
184
  explanation = ContentAST.Section()
185
185
 
@@ -6,8 +6,8 @@ import math
6
6
  import numpy as np
7
7
  from typing import List, Tuple, Dict, Any
8
8
 
9
- from QuizGenerator.contentast import ContentAST
10
- from QuizGenerator.question import Question, Answer, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
10
+ from QuizGenerator.question import Question, QuestionRegistry
11
11
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
12
12
 
13
13
  log = logging.getLogger(__name__)
@@ -73,12 +73,12 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
73
73
 
74
74
  # Individual loss answers
75
75
  for i in range(self.num_samples):
76
- self.answers[f"loss_{i}"] = Answer.float_value(f"loss_{i}", self.individual_losses[i], label=f"Sample {i+1} loss")
76
+ self.answers[f"loss_{i}"] = AnswerTypes.Float(self.individual_losses[i], label=f"Sample {i + 1} loss")
77
77
 
78
78
  # Overall loss answer
79
- self.answers["overall_loss"] = Answer.float_value("overall_loss", self.overall_loss, label="Overall loss")
79
+ self.answers["overall_loss"] = AnswerTypes.Float(self.overall_loss, label="Overall loss")
80
80
 
81
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Element, List[Answer]]:
81
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
82
82
  """Build question body and collect answers."""
83
83
  body = ContentAST.Section()
84
84
  answers = []
@@ -115,7 +115,7 @@ class LossQuestion(Question, TableQuestionMixin, BodyTemplatesMixin, abc.ABC):
115
115
  """Create the data table with answer fields."""
116
116
  pass
117
117
 
118
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Element, List[Answer]]:
118
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
119
119
  """Build question explanation."""
120
120
  explanation = ContentAST.Section()
121
121
 
@@ -2,7 +2,7 @@
2
2
  from typing import List, Tuple, Callable, Union, Any
3
3
  import sympy as sp
4
4
 
5
- from QuizGenerator.misc import Answer
5
+ from QuizGenerator.contentast import ContentAST
6
6
 
7
7
  def generate_function(rng, num_variables: int, max_degree: int, use_quadratic: bool = True) -> tuple[Any, sp.Expr, sp.MutableDenseMatrix, sp.Equality]:
8
8
  """
@@ -61,7 +61,7 @@ def format_vector(vec: List[float]) -> str:
61
61
 
62
62
  vector_string = ', '.join(
63
63
  [
64
- sorted(Answer.accepted_strings(v), key=lambda s: len(s))[0]
64
+ sorted(ContentAST.Answer.accepted_strings(v), key=lambda s: len(s))[0]
65
65
  for v in vec
66
66
  ]
67
67
  )
@@ -2,8 +2,8 @@
2
2
  import abc
3
3
  import logging
4
4
 
5
- from QuizGenerator.question import Question, QuestionRegistry, Answer
6
- from QuizGenerator.contentast import ContentAST
5
+ from QuizGenerator.question import Question, QuestionRegistry
6
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
7
7
  from QuizGenerator.mixins import MathOperationQuestion
8
8
 
9
9
  log = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
21
21
  - ContentAST.Matrix for mathematical matrices
22
22
  - ContentAST.Equation.make_block_equation__multiline_equals for step-by-step solutions
23
23
  - ContentAST.OnlyHtml for Canvas-specific content
24
- - Answer.integer for numerical answers
24
+ - ContentAST.Answer.integer for numerical answers
25
25
  """
26
26
  def __init__(self, *args, **kwargs):
27
27
  kwargs["topic"] = kwargs.get("topic", Question.Topic.MATH)
@@ -169,7 +169,7 @@ class MatrixAddition(MatrixMathQuestion):
169
169
  for i in range(rows):
170
170
  for j in range(cols):
171
171
  answer_key = f"answer_{i}_{j}"
172
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
172
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
173
173
  else:
174
174
  # For multipart questions, use subpart letter format
175
175
  letter = chr(ord('a') + subpart_index)
@@ -178,7 +178,7 @@ class MatrixAddition(MatrixMathQuestion):
178
178
  for i in range(rows):
179
179
  for j in range(cols):
180
180
  answer_key = f"subpart_{letter}_{i}_{j}"
181
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
181
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
182
182
 
183
183
  def refresh(self, *args, **kwargs):
184
184
  """Override refresh to set rows/cols for compatibility."""
@@ -299,7 +299,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
299
299
  for i in range(rows):
300
300
  for j in range(cols):
301
301
  answer_key = f"answer_{i}_{j}"
302
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
302
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
303
303
  else:
304
304
  # For multipart questions, use subpart letter format
305
305
  letter = chr(ord('a') + subpart_index)
@@ -308,7 +308,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
308
308
  for i in range(rows):
309
309
  for j in range(cols):
310
310
  answer_key = f"subpart_{letter}_{i}_{j}"
311
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
311
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
312
312
 
313
313
  def refresh(self, *args, **kwargs):
314
314
  """Override refresh to handle different scalars per subpart."""
@@ -501,31 +501,27 @@ class MatrixMultiplication(MatrixMathQuestion):
501
501
  # For single questions, use the old answer format
502
502
  # Dimension answers
503
503
  if result is not None:
504
- self.answers["result_rows"] = Answer.integer("result_rows", self.result_rows,
505
- label="Number of rows in result")
506
- self.answers["result_cols"] = Answer.integer("result_cols", self.result_cols,
507
- label="Number of columns in result")
504
+ self.answers["result_rows"] = AnswerTypes.Int(self.result_rows, label="Number of rows in result")
505
+ self.answers["result_cols"] = AnswerTypes.Int(self.result_cols, label="Number of columns in result")
508
506
 
509
507
  # Matrix element answers
510
508
  for i in range(self.max_dim):
511
509
  for j in range(self.max_dim):
512
510
  answer_key = f"answer_{i}_{j}"
513
511
  if i < self.result_rows and j < self.result_cols:
514
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
512
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
515
513
  else:
516
- self.answers[answer_key] = Answer.string(answer_key, "-")
514
+ self.answers[answer_key] = AnswerTypes.String("-")
517
515
  else:
518
516
  # Multiplication not possible
519
- self.answers["result_rows"] = Answer.string("result_rows", "-",
520
- label="Number of rows in result")
521
- self.answers["result_cols"] = Answer.string("result_cols", "-",
522
- label="Number of columns in result")
517
+ self.answers["result_rows"] = AnswerTypes.String("-", label="Number of rows in result")
518
+ self.answers["result_cols"] = AnswerTypes.String("-", label="Number of columns in result")
523
519
 
524
520
  # All matrix elements are "-"
525
521
  for i in range(self.max_dim):
526
522
  for j in range(self.max_dim):
527
523
  answer_key = f"answer_{i}_{j}"
528
- self.answers[answer_key] = Answer.string(answer_key, "-")
524
+ self.answers[answer_key] = AnswerTypes.String("-")
529
525
  else:
530
526
  # For multipart questions, use subpart letter format
531
527
  letter = chr(ord('a') + subpart_index)
@@ -537,7 +533,7 @@ class MatrixMultiplication(MatrixMathQuestion):
537
533
  for i in range(rows):
538
534
  for j in range(cols):
539
535
  answer_key = f"subpart_{letter}_{i}_{j}"
540
- self.answers[answer_key] = Answer.integer(answer_key, result[i][j])
536
+ self.answers[answer_key] = AnswerTypes.Int(result[i][j])
541
537
 
542
538
  def _add_single_question_answers(self, body):
543
539
  """Add Canvas-only answer fields for MatrixMultiplication with dash instruction.