QuizGenerator 0.4.3__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 +126 -53
  22. QuizGenerator/question.py +110 -15
  23. QuizGenerator/quiz.py +74 -23
  24. QuizGenerator/regenerate.py +98 -29
  25. {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/METADATA +1 -1
  26. {quizgenerator-0.4.3.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.3.dist-info → quizgenerator-0.5.0.dist-info}/WHEEL +0 -0
  30. {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/entry_points.txt +0 -0
  31. {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -54,7 +54,15 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
54
54
  ),
55
55
  })
56
56
 
57
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
57
+ def _get_body(self, *args, **kwargs):
58
+ """Build question body and collect answers."""
59
+ answers = [
60
+ self.answers["answer__rotational_delay"],
61
+ self.answers["answer__access_delay"],
62
+ self.answers["answer__transfer_delay"],
63
+ self.answers["answer__disk_access_delay"],
64
+ ]
65
+
58
66
  # Create parameter info table using mixin
59
67
  parameter_info = {
60
68
  "Hard Drive Rotation Speed": f"{self.hard_drive_rotation_speed}RPM",
@@ -84,8 +92,8 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
84
92
  intro_text = "Given the information below, please calculate the following values."
85
93
 
86
94
  instructions = (
87
- f"Make sure your answers are rounded to {Answer.DEFAULT_ROUNDING_DIGITS} decimal points "
88
- f"(even if they are whole numbers), and do so after you finish all your calculations! "
95
+ f"Make sure that if you round your answers you use the unrounded values for your final calculations, "
96
+ f"otherwise you may introduce error into your calculations."
89
97
  f"(i.e. don't use your rounded answers to calculate your overall answer)"
90
98
  )
91
99
 
@@ -96,9 +104,14 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
96
104
  additional_instructions=instructions
97
105
  )
98
106
 
107
+ return body, answers
108
+
109
+ def get_body(self, *args, **kwargs) -> ContentAST.Section:
110
+ """Build question body (backward compatible interface)."""
111
+ body, _ = self._get_body(*args, **kwargs)
99
112
  return body
100
-
101
- def get_explanation(self) -> ContentAST.Section:
113
+
114
+ def _get_explanation(self):
102
115
  explanation = ContentAST.Section()
103
116
 
104
117
  explanation.add_element(
@@ -150,6 +163,11 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
150
163
  f"= {self.number_of_reads} \\cdot {self.access_delay:0.2f} + {self.transfer_delay:0.2f} "
151
164
  f"= {self.disk_access_delay:0.2f}ms")
152
165
  ])
166
+ return explanation, []
167
+
168
+ def get_explanation(self) -> ContentAST.Section:
169
+ """Build question explanation (backward compatible interface)."""
170
+ explanation, _ = self._get_explanation()
153
171
  return explanation
154
172
 
155
173
 
@@ -178,7 +196,15 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
178
196
  "answer__inode_index_in_block": Answer.integer("answer__inode_index_in_block", self.inode_index_in_block),
179
197
  })
180
198
 
181
- def get_body(self) -> ContentAST.Section:
199
+ def _get_body(self):
200
+ """Build question body and collect answers."""
201
+ answers = [
202
+ self.answers["answer__inode_address"],
203
+ self.answers["answer__inode_block"],
204
+ self.answers["answer__inode_address_in_block"],
205
+ self.answers["answer__inode_index_in_block"],
206
+ ]
207
+
182
208
  # Create parameter info table using mixin
183
209
  parameter_info = {
184
210
  "Block Size": f"{self.block_size} Bytes",
@@ -206,21 +232,21 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
206
232
  # Use mixin to create complete body with both tables
207
233
  intro_text = "Given the information below, please calculate the following values."
208
234
 
209
- instructions = (
210
- "(hint: they should all be round numbers). "
211
- "Remember, demonstrating you know the equations and what goes into them is generally sufficient."
212
- )
213
-
214
235
  body = self.create_parameter_calculation_body(
215
236
  intro_text=intro_text,
216
237
  parameter_table=parameter_table,
217
238
  answer_table=answer_table,
218
- additional_instructions=instructions
239
+ # additional_instructions=instructions
219
240
  )
220
241
 
242
+ return body, answers
243
+
244
+ def get_body(self) -> ContentAST.Section:
245
+ """Build question body (backward compatible interface)."""
246
+ body, _ = self._get_body()
221
247
  return body
222
-
223
- def get_explanation(self) -> ContentAST.Section:
248
+
249
+ def _get_explanation(self):
224
250
  explanation = ContentAST.Section()
225
251
 
226
252
  explanation.add_element(
@@ -292,7 +318,12 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
292
318
  f"{self.inode_index_in_block}"
293
319
  ]
294
320
  ))
295
-
321
+
322
+ return explanation, []
323
+
324
+ def get_explanation(self) -> ContentAST.Section:
325
+ """Build question explanation (backward compatible interface)."""
326
+ explanation, _ = self._get_explanation()
296
327
  return explanation
297
328
 
298
329
 
@@ -330,40 +361,42 @@ class VSFS_states(IOQuestion):
330
361
  f"{operations[-1]['cmd']}",
331
362
  kind=Answer.AnswerKind.MULTIPLE_DROPDOWN,
332
363
  correct=True,
333
- baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']]))
364
+ baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']])),
365
+ label="Command"
334
366
  )
335
367
 
336
- def get_body(self) -> ContentAST.Section:
368
+ def _get_body(self):
369
+ """Build question body and collect answers."""
370
+ answers = [self.answers["answer__cmd"]]
371
+
337
372
  body = ContentAST.Section()
338
-
373
+
339
374
  body.add_element(ContentAST.Paragraph(["What operation happens between these two states?"]))
340
-
375
+
341
376
  body.add_element(
342
377
  ContentAST.Code(
343
378
  self.start_state,
344
379
  make_small=True
345
380
  )
346
381
  )
347
-
348
- body.add_element(
349
- ContentAST.AnswerBlock(
350
- ContentAST.Answer(
351
- self.answers["answer__cmd"],
352
- label="Command"
353
- )
354
- )
355
- )
356
-
382
+
383
+ body.add_element(ContentAST.AnswerBlock(self.answers["answer__cmd"]))
384
+
357
385
  body.add_element(
358
386
  ContentAST.Code(
359
387
  self.end_state,
360
388
  make_small=True
361
389
  )
362
390
  )
363
-
391
+
392
+ return body, answers
393
+
394
+ def get_body(self) -> ContentAST.Section:
395
+ """Build question body (backward compatible interface)."""
396
+ body, _ = self._get_body()
364
397
  return body
365
-
366
- def get_explanation(self) -> ContentAST.Section:
398
+
399
+ def _get_explanation(self):
367
400
  explanation = ContentAST.Section()
368
401
 
369
402
  log.debug(f"self.start_state: {self.start_state}")
@@ -446,6 +479,11 @@ class VSFS_states(IOQuestion):
446
479
  highlight_changes(self.start_state, self.end_state)
447
480
  )
448
481
  )
449
-
482
+
483
+ return explanation, []
484
+
485
+ def get_explanation(self) -> ContentAST.Section:
486
+ """Build question explanation (backward compatible interface)."""
487
+ explanation, _ = self._get_explanation()
450
488
  return explanation
451
489
 
@@ -368,18 +368,28 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
368
368
  self.answers.update({
369
369
  "answer__average_response_time": Answer.auto_float(
370
370
  "answer__average_response_time",
371
- sum([job.response_time for job in jobs]) / len(jobs)
371
+ sum([job.response_time for job in jobs]) / len(jobs),
372
+ label="Overall average response time"
372
373
  ),
373
374
  "answer__average_turnaround_time": Answer.auto_float(
374
375
  "answer__average_turnaround_time",
375
- sum([job.turnaround_time for job in jobs]) / len(jobs)
376
+ sum([job.turnaround_time for job in jobs]) / len(jobs),
377
+ label="Overall average TAT"
376
378
  )
377
379
  })
378
380
 
379
381
  # Return whether this workload is interesting
380
382
  return self.is_interesting()
381
383
 
382
- def get_body(self, *args, **kwargs) -> ContentAST.Section:
384
+ def _get_body(self, *args, **kwargs):
385
+ """
386
+ Build question body and collect answers.
387
+ Returns:
388
+ Tuple of (body_ast, answers_list)
389
+ """
390
+ from typing import List
391
+ answers: List[Answer] = []
392
+
383
393
  # Create table data for scheduling results
384
394
  table_rows = []
385
395
  for job_id in sorted(self.job_stats.keys()):
@@ -390,6 +400,9 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
390
400
  "Response Time": f"answer__response_time_job{job_id}", # Answer key
391
401
  "TAT": f"answer__turnaround_time_job{job_id}" # Answer key
392
402
  })
403
+ # Collect answers for this job
404
+ answers.append(self.answers[f"answer__response_time_job{job_id}"])
405
+ answers.append(self.answers[f"answer__turnaround_time_job{job_id}"])
393
406
 
394
407
  # Create table using mixin
395
408
  scheduling_table = self.create_answer_table(
@@ -398,15 +411,18 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
398
411
  answer_columns=["Response Time", "TAT"]
399
412
  )
400
413
 
414
+ # Collect average answers
415
+ avg_response_answer = self.answers["answer__average_response_time"]
416
+ avg_tat_answer = self.answers["answer__average_turnaround_time"]
417
+ answers.append(avg_response_answer)
418
+ answers.append(avg_tat_answer)
419
+
401
420
  # Create average answer block
402
- average_block = ContentAST.AnswerBlock([
403
- ContentAST.Answer(self.answers["answer__average_response_time"], label="Overall average response time"),
404
- ContentAST.Answer(self.answers["answer__average_turnaround_time"], label="Overall average TAT")
405
- ])
421
+ average_block = ContentAST.AnswerBlock([avg_response_answer, avg_tat_answer])
406
422
 
407
423
  # Use mixin to create complete body
408
424
  intro_text = (
409
- f"Given the below information, compute the required values if using <b>{self.scheduler_algorithm}</b> scheduling. "
425
+ f"Given the below information, compute the required values if using **{self.scheduler_algorithm}** scheduling. "
410
426
  f"Break any ties using the job number."
411
427
  )
412
428
 
@@ -418,18 +434,28 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
418
434
 
419
435
  body = self.create_fill_in_table_body(intro_text, instructions, scheduling_table)
420
436
  body.add_element(average_block)
437
+ return body, answers
438
+
439
+ def get_body(self, *args, **kwargs) -> ContentAST.Section:
440
+ """Build question body (backward compatible interface)."""
441
+ body, _ = self._get_body(*args, **kwargs)
421
442
  return body
422
443
 
423
- def get_explanation(self, **kwargs) -> ContentAST.Section:
444
+ def _get_explanation(self, **kwargs):
445
+ """
446
+ Build question explanation.
447
+ Returns:
448
+ Tuple of (explanation_ast, answers_list)
449
+ """
424
450
  explanation = ContentAST.Section()
425
-
451
+
426
452
  explanation.add_element(
427
453
  ContentAST.Paragraph([
428
454
  f"To calculate the overall Turnaround and Response times using {self.scheduler_algorithm} "
429
455
  f"we want to first start by calculating the respective target and response times of all of our individual jobs."
430
456
  ])
431
457
  )
432
-
458
+
433
459
  explanation.add_elements([
434
460
  ContentAST.Paragraph([
435
461
  "We do this by subtracting arrival time from either the completion time or the start time. That is:"
@@ -437,13 +463,13 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
437
463
  ContentAST.Equation("Job_{TAT} = Job_{completion} - Job_{arrival\_time}"),
438
464
  ContentAST.Equation("Job_{response} = Job_{start} - Job_{arrival\_time}"),
439
465
  ])
440
-
466
+
441
467
  explanation.add_element(
442
468
  ContentAST.Paragraph([
443
469
  f"For each of our {len(self.job_stats.keys())} jobs, we can make these calculations.",
444
470
  ])
445
471
  )
446
-
472
+
447
473
  ## Add in TAT
448
474
  explanation.add_element(
449
475
  ContentAST.Paragraph([
@@ -456,7 +482,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
456
482
  for job_id in sorted(self.job_stats.keys())
457
483
  ])
458
484
  )
459
-
485
+
460
486
  summation_line = ' + '.join([
461
487
  f"{self.job_stats[job_id]['TAT']:0.{self.ROUNDING_DIGITS}f}" for job_id in sorted(self.job_stats.keys())
462
488
  ])
@@ -467,8 +493,8 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
467
493
  f"= {self.overall_stats['TAT']:0.{self.ROUNDING_DIGITS}f}",
468
494
  ])
469
495
  )
470
-
471
-
496
+
497
+
472
498
  ## Add in Response
473
499
  explanation.add_element(
474
500
  ContentAST.Paragraph([
@@ -481,7 +507,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
481
507
  for job_id in sorted(self.job_stats.keys())
482
508
  ])
483
509
  )
484
-
510
+
485
511
  summation_line = ' + '.join([
486
512
  f"{self.job_stats[job_id]['Response']:0.{self.ROUNDING_DIGITS}f}" for job_id in sorted(self.job_stats.keys())
487
513
  ])
@@ -494,7 +520,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
494
520
  "\n",
495
521
  ])
496
522
  )
497
-
523
+
498
524
  explanation.add_element(
499
525
  ContentAST.Table(
500
526
  headers=["Time", "Events"],
@@ -504,14 +530,19 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
504
530
  ]
505
531
  )
506
532
  )
507
-
533
+
508
534
  explanation.add_element(
509
535
  ContentAST.Picture(
510
536
  img_data=self.make_image(),
511
537
  caption="Process Scheduling Overview"
512
538
  )
513
539
  )
514
-
540
+
541
+ return explanation, []
542
+
543
+ def get_explanation(self, **kwargs) -> ContentAST.Section:
544
+ """Build question explanation (backward compatible interface)."""
545
+ explanation, _ = self._get_explanation(**kwargs)
515
546
  return explanation
516
547
 
517
548
  def is_interesting(self) -> bool:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import abc
4
4
  import logging
5
- from typing import List
5
+ from typing import List, Tuple
6
6
  import sympy as sp
7
7
 
8
8
  from QuizGenerator.contentast import ContentAST
@@ -39,6 +39,9 @@ class DerivativeQuestion(Question, abc.ABC):
39
39
  # Evaluate gradient at the specified point
40
40
  subs_map = dict(zip(self.variables, evaluation_point))
41
41
 
42
+ # Format evaluation point for label
43
+ eval_point_str = ", ".join([f"x_{i} = {evaluation_point[i]}" for i in range(self.num_variables)])
44
+
42
45
  # Create answer for each partial derivative
43
46
  for i in range(self.num_variables):
44
47
  answer_key = f"partial_derivative_{i}"
@@ -52,7 +55,9 @@ class DerivativeQuestion(Question, abc.ABC):
52
55
  raise ValueError("Complex number encountered - need to regenerate")
53
56
 
54
57
  # Use auto_float for Canvas compatibility with integers and decimals
55
- self.answers[answer_key] = Answer.auto_float(answer_key, gradient_value)
58
+ # Label includes the partial derivative notation
59
+ label = f"∂f/∂x_{i} at ({eval_point_str})"
60
+ self.answers[answer_key] = Answer.auto_float(answer_key, gradient_value, label=label)
56
61
 
57
62
  def _create_gradient_vector_answer(self) -> None:
58
63
  """Create a single gradient vector answer for PDF format."""
@@ -72,8 +77,10 @@ class DerivativeQuestion(Question, abc.ABC):
72
77
  vector_str = format_vector(gradient_values)
73
78
  self.answers["gradient_vector"] = Answer.string("gradient_vector", vector_str, pdf_only=True)
74
79
 
75
- def get_body(self, **kwargs) -> ContentAST.Section:
80
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
81
+ """Build question body and collect answers."""
76
82
  body = ContentAST.Section()
83
+ answers = []
77
84
 
78
85
  # Display the function
79
86
  body.add_element(
@@ -103,6 +110,8 @@ class DerivativeQuestion(Question, abc.ABC):
103
110
 
104
111
  # For Canvas: Use OnlyHtml to show individual partial derivatives
105
112
  for i in range(self.num_variables):
113
+ answer = self.answers[f"partial_derivative_{i}"]
114
+ answers.append(answer)
106
115
  body.add_element(
107
116
  ContentAST.OnlyHtml([
108
117
  ContentAST.Paragraph([
@@ -110,14 +119,20 @@ class DerivativeQuestion(Question, abc.ABC):
110
119
  f"\\left. {self._format_partial_derivative(i)} \\right|_{{{eval_point_str}}} = ",
111
120
  inline=True
112
121
  ),
113
- ContentAST.Answer(self.answers[f"partial_derivative_{i}"])
122
+ answer
114
123
  ])
115
124
  ])
116
125
  )
117
126
 
127
+ return body, answers
128
+
129
+ def get_body(self, **kwargs) -> ContentAST.Section:
130
+ """Build question body (backward compatible interface)."""
131
+ body, _ = self._get_body(**kwargs)
118
132
  return body
119
133
 
120
- def get_explanation(self, **kwargs) -> ContentAST.Section:
134
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
135
+ """Build question explanation."""
121
136
  explanation = ContentAST.Section()
122
137
 
123
138
  # Show the function and its gradient
@@ -165,6 +180,11 @@ class DerivativeQuestion(Question, abc.ABC):
165
180
  ])
166
181
  )
167
182
 
183
+ return explanation, []
184
+
185
+ def get_explanation(self, **kwargs) -> ContentAST.Section:
186
+ """Build question explanation (backward compatible interface)."""
187
+ explanation, _ = self._get_explanation(**kwargs)
168
188
  return explanation
169
189
 
170
190
 
@@ -264,7 +284,8 @@ class DerivativeChain(DerivativeQuestion):
264
284
  f = sp.Function('f')
265
285
  self.equation = sp.Eq(f(*self.variables), self.function)
266
286
 
267
- def get_explanation(self, **kwargs) -> ContentAST.Section:
287
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
288
+ """Build question explanation."""
268
289
  explanation = ContentAST.Section()
269
290
 
270
291
  # Show the composed function structure
@@ -366,4 +387,9 @@ class DerivativeChain(DerivativeQuestion):
366
387
  ])
367
388
  )
368
389
 
390
+ return explanation, []
391
+
392
+ def get_explanation(self, **kwargs) -> ContentAST.Section:
393
+ """Build question explanation (backward compatible interface)."""
394
+ explanation, _ = self._get_explanation(**kwargs)
369
395
  return explanation