QuizGenerator 0.7.0__py3-none-any.whl → 0.8.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 +6 -6
  2. QuizGenerator/generate.py +2 -1
  3. QuizGenerator/mixins.py +14 -100
  4. QuizGenerator/premade_questions/basic.py +24 -29
  5. QuizGenerator/premade_questions/cst334/languages.py +100 -99
  6. QuizGenerator/premade_questions/cst334/math_questions.py +112 -122
  7. QuizGenerator/premade_questions/cst334/memory_questions.py +621 -621
  8. QuizGenerator/premade_questions/cst334/persistence_questions.py +137 -163
  9. QuizGenerator/premade_questions/cst334/process.py +312 -322
  10. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +34 -35
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +41 -36
  12. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +48 -41
  13. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +285 -520
  14. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -126
  15. QuizGenerator/premade_questions/cst463/models/attention.py +44 -50
  16. QuizGenerator/premade_questions/cst463/models/cnns.py +43 -47
  17. QuizGenerator/premade_questions/cst463/models/matrices.py +61 -11
  18. QuizGenerator/premade_questions/cst463/models/rnns.py +48 -50
  19. QuizGenerator/premade_questions/cst463/models/text.py +65 -67
  20. QuizGenerator/premade_questions/cst463/models/weight_counting.py +47 -46
  21. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +100 -156
  22. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +93 -141
  23. QuizGenerator/question.py +273 -202
  24. QuizGenerator/quiz.py +8 -5
  25. QuizGenerator/regenerate.py +128 -19
  26. {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/METADATA +30 -2
  27. {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/RECORD +30 -30
  28. {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/WHEEL +0 -0
  29. {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/entry_points.txt +0 -0
  30. {quizgenerator-0.7.0.dist-info → quizgenerator-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ import logging
6
6
  import re
7
7
  import numpy as np
8
8
  import sympy as sp
9
- from typing import List, Tuple, Dict, Any
9
+ from typing import List, Tuple
10
10
 
11
11
  import QuizGenerator.contentast as ca
12
12
  from QuizGenerator.question import Question, QuestionRegistry
@@ -39,8 +39,13 @@ class ParameterCountingQuestion(Question):
39
39
  self.num_layers = kwargs.get("num_layers", None)
40
40
  self.include_biases = kwargs.get("include_biases", True)
41
41
 
42
- def refresh(self, rng_seed=None, *args, **kwargs):
43
- super().refresh(rng_seed=rng_seed, *args, **kwargs)
42
+ def _build_context(self, *, rng_seed=None, **kwargs):
43
+ if "num_layers" in kwargs:
44
+ self.num_layers = kwargs.get("num_layers", self.num_layers)
45
+ if "include_biases" in kwargs:
46
+ self.include_biases = kwargs.get("include_biases", self.include_biases)
47
+
48
+ self.rng.seed(rng_seed)
44
49
 
45
50
  # Generate random architecture
46
51
  if self.num_layers is None:
@@ -78,22 +83,11 @@ class ParameterCountingQuestion(Question):
78
83
 
79
84
  self.total_params = self.total_weights + self.total_biases
80
85
 
81
- # Create answers
82
- self._create_answers()
83
-
84
- def _create_answers(self):
85
- """Create answer fields."""
86
- self.answers = {}
87
-
88
- self.answers["total_weights"] = ca.AnswerTypes.Int(self.total_weights, label="Total weights")
89
-
90
- if self.include_biases:
91
- self.answers["total_biases"] = ca.AnswerTypes.Int(self.total_biases, label="Total biases")
92
- self.answers["total_params"] = ca.AnswerTypes.Int(self.total_params, label="Total trainable parameters")
93
- else:
94
- self.answers["total_params"] = ca.AnswerTypes.Int(self.total_params, label="Total trainable parameters")
86
+ context = dict(kwargs)
87
+ context["rng_seed"] = rng_seed
88
+ return context
95
89
 
96
- def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
90
+ def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
97
91
  """Build question body and collect answers."""
98
92
  body = ca.Section()
99
93
  answers = []
@@ -124,35 +118,35 @@ class ParameterCountingQuestion(Question):
124
118
  table_data = []
125
119
  table_data.append(["Parameter Type", "Count"])
126
120
 
127
- answers.append(self.answers["total_weights"])
121
+ total_weights_answer = ca.AnswerTypes.Int(self.total_weights, label="Total weights")
122
+ total_biases_answer = None
123
+ total_params_answer = ca.AnswerTypes.Int(self.total_params, label="Total trainable parameters")
124
+
125
+ answers.append(total_weights_answer)
128
126
  table_data.append([
129
127
  "Total weights (connections between layers)",
130
- self.answers["total_weights"]
128
+ total_weights_answer
131
129
  ])
132
130
 
133
131
  if self.include_biases:
134
- answers.append(self.answers["total_biases"])
132
+ total_biases_answer = ca.AnswerTypes.Int(self.total_biases, label="Total biases")
133
+ answers.append(total_biases_answer)
135
134
  table_data.append([
136
135
  "Total biases",
137
- self.answers["total_biases"]
136
+ total_biases_answer
138
137
  ])
139
138
 
140
- answers.append(self.answers["total_params"])
139
+ answers.append(total_params_answer)
141
140
  table_data.append([
142
141
  "Total trainable parameters",
143
- self.answers["total_params"]
142
+ total_params_answer
144
143
  ])
145
144
 
146
145
  body.add_element(ca.Table(data=table_data))
147
146
 
148
147
  return body, answers
149
148
 
150
- def get_body(self, **kwargs) -> ca.Section:
151
- """Build question body (backward compatible interface)."""
152
- body, _ = self._get_body(**kwargs)
153
- return body
154
-
155
- def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
149
+ def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
156
150
  """Build question explanation."""
157
151
  explanation = ca.Section()
158
152
 
@@ -221,11 +215,6 @@ class ParameterCountingQuestion(Question):
221
215
 
222
216
  return explanation, []
223
217
 
224
- def get_explanation(self, **kwargs) -> ca.Section:
225
- """Build question explanation (backward compatible interface)."""
226
- explanation, _ = self._get_explanation(**kwargs)
227
- return explanation
228
-
229
218
 
230
219
  @QuestionRegistry.register()
231
220
  class ActivationFunctionComputationQuestion(Question):
@@ -248,8 +237,13 @@ class ActivationFunctionComputationQuestion(Question):
248
237
  self.vector_size = kwargs.get("vector_size", None)
249
238
  self.activation = kwargs.get("activation", None)
250
239
 
251
- def refresh(self, rng_seed=None, *args, **kwargs):
252
- super().refresh(rng_seed=rng_seed, *args, **kwargs)
240
+ def _build_context(self, *, rng_seed=None, **kwargs):
241
+ if "vector_size" in kwargs:
242
+ self.vector_size = kwargs.get("vector_size", self.vector_size)
243
+ if "activation" in kwargs:
244
+ self.activation = kwargs.get("activation", self.activation)
245
+
246
+ self.rng.seed(rng_seed)
253
247
 
254
248
  # Generate random input vector
255
249
  if self.vector_size is None:
@@ -276,8 +270,9 @@ class ActivationFunctionComputationQuestion(Question):
276
270
  # Compute outputs
277
271
  self.output_vector = self._compute_activation(self.input_vector)
278
272
 
279
- # Create answers
280
- self._create_answers()
273
+ context = dict(kwargs)
274
+ context["rng_seed"] = rng_seed
275
+ return context
281
276
 
282
277
  def _compute_activation(self, inputs):
283
278
  """Compute activation function output."""
@@ -325,20 +320,7 @@ class ActivationFunctionComputationQuestion(Question):
325
320
 
326
321
  return ""
327
322
 
328
- def _create_answers(self):
329
- """Create answer fields."""
330
- self.answers = {}
331
-
332
- if self.activation == self.ACTIVATION_SOFTMAX:
333
- # Softmax: single vector answer
334
- self.answers["output"] = ca.AnswerTypes.Vector(self.output_vector, label="Output vector")
335
- else:
336
- # Element-wise: individual answers
337
- for i, output in enumerate(self.output_vector):
338
- key = f"output_{i}"
339
- self.answers[key] = ca.AnswerTypes.Float(float(output), label=f"Output for input {self.input_vector[i]:.1f}")
340
-
341
- def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
323
+ def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
342
324
  """Build question body and collect answers."""
343
325
  body = ca.Section()
344
326
  answers = []
@@ -367,10 +349,11 @@ class ActivationFunctionComputationQuestion(Question):
367
349
  "Compute the output vector:"
368
350
  ]))
369
351
 
370
- answers.append(self.answers["output"])
352
+ output_answer = ca.AnswerTypes.Vector(self.output_vector, label="Output vector")
353
+ answers.append(output_answer)
371
354
  table_data = []
372
355
  table_data.append(["Output Vector"])
373
- table_data.append([self.answers["output"]])
356
+ table_data.append([output_answer])
374
357
 
375
358
  body.add_element(ca.Table(data=table_data))
376
359
 
@@ -383,7 +366,10 @@ class ActivationFunctionComputationQuestion(Question):
383
366
  table_data.append(["Input", "Output"])
384
367
 
385
368
  for i, x in enumerate(self.input_vector):
386
- answer = self.answers[f"output_{i}"]
369
+ answer = ca.AnswerTypes.Float(
370
+ float(self.output_vector[i]),
371
+ label=f"Output for input {self.input_vector[i]:.1f}"
372
+ )
387
373
  answers.append(answer)
388
374
  table_data.append([
389
375
  ca.Equation(f"{x:.1f}", inline=True),
@@ -394,12 +380,7 @@ class ActivationFunctionComputationQuestion(Question):
394
380
 
395
381
  return body, answers
396
382
 
397
- def get_body(self, **kwargs) -> ca.Section:
398
- """Build question body (backward compatible interface)."""
399
- body, _ = self._get_body(**kwargs)
400
- return body
401
-
402
- def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
383
+ def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
403
384
  """Build question explanation."""
404
385
  explanation = ca.Section()
405
386
 
@@ -470,11 +451,6 @@ class ActivationFunctionComputationQuestion(Question):
470
451
 
471
452
  return explanation, []
472
453
 
473
- def get_explanation(self, **kwargs) -> ca.Section:
474
- """Build question explanation (backward compatible interface)."""
475
- explanation, _ = self._get_explanation(**kwargs)
476
- return explanation
477
-
478
454
 
479
455
  @QuestionRegistry.register()
480
456
  class RegularizationCalculationQuestion(Question):
@@ -495,8 +471,11 @@ class RegularizationCalculationQuestion(Question):
495
471
 
496
472
  self.num_weights = kwargs.get("num_weights", None)
497
473
 
498
- def refresh(self, rng_seed=None, *args, **kwargs):
499
- super().refresh(rng_seed=rng_seed, *args, **kwargs)
474
+ def _build_context(self, *, rng_seed=None, **kwargs):
475
+ if "num_weights" in kwargs:
476
+ self.num_weights = kwargs.get("num_weights", self.num_weights)
477
+
478
+ self.rng.seed(rng_seed)
500
479
 
501
480
  # Generate small network (2-4 weights for simplicity)
502
481
  if self.num_weights is None:
@@ -538,20 +517,11 @@ class RegularizationCalculationQuestion(Question):
538
517
  self.grad_reg_w0 = self.lambda_reg * self.weights[0]
539
518
  self.grad_total_w0 = self.grad_base_w0 + self.grad_reg_w0
540
519
 
541
- # Create answers
542
- self._create_answers()
543
-
544
- def _create_answers(self):
545
- """Create answer fields."""
546
- self.answers = {}
547
-
548
- self.answers["prediction"] = ca.AnswerTypes.Float(float(self.prediction), label="Prediction ŷ")
549
- self.answers["base_loss"] = ca.AnswerTypes.Float(float(self.base_loss), label="Base MSE loss")
550
- self.answers["l2_penalty"] = ca.AnswerTypes.Float(float(self.l2_penalty), label="L2 penalty")
551
- self.answers["total_loss"] = ca.AnswerTypes.Float(float(self.total_loss), label="Total loss")
552
- self.answers["grad_total_w0"] = ca.AnswerTypes.Float(float(self.grad_total_w0), label="Gradient ∂L/∂w₀")
520
+ context = dict(kwargs)
521
+ context["rng_seed"] = rng_seed
522
+ return context
553
523
 
554
- def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
524
+ def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
555
525
  """Build question body and collect answers."""
556
526
  body = ca.Section()
557
527
  answers = []
@@ -604,46 +574,47 @@ class RegularizationCalculationQuestion(Question):
604
574
  table_data = []
605
575
  table_data.append(["Calculation", "Value"])
606
576
 
607
- answers.append(self.answers["prediction"])
577
+ prediction_answer = ca.AnswerTypes.Float(float(self.prediction), label="Prediction ŷ")
578
+ base_loss_answer = ca.AnswerTypes.Float(float(self.base_loss), label="Base MSE loss")
579
+ l2_penalty_answer = ca.AnswerTypes.Float(float(self.l2_penalty), label="L2 penalty")
580
+ total_loss_answer = ca.AnswerTypes.Float(float(self.total_loss), label="Total loss")
581
+ grad_total_w0_answer = ca.AnswerTypes.Float(float(self.grad_total_w0), label="Gradient ∂L/∂w₀")
582
+
583
+ answers.append(prediction_answer)
608
584
  table_data.append([
609
585
  ca.Paragraph(["Prediction ", ca.Equation(r"\hat{y}", inline=True)]),
610
- self.answers["prediction"]
586
+ prediction_answer
611
587
  ])
612
588
 
613
- answers.append(self.answers["base_loss"])
589
+ answers.append(base_loss_answer)
614
590
  table_data.append([
615
591
  ca.Paragraph(["Base MSE loss: ", ca.Equation(r"L_{base} = (1/2)(y - \hat{y})^2", inline=True)]),
616
- self.answers["base_loss"]
592
+ base_loss_answer
617
593
  ])
618
594
 
619
- answers.append(self.answers["l2_penalty"])
595
+ answers.append(l2_penalty_answer)
620
596
  table_data.append([
621
597
  ca.Paragraph(["L2 penalty: ", ca.Equation(r"L_{reg} = (\lambda/2)\sum w_i^2", inline=True)]),
622
- self.answers["l2_penalty"]
598
+ l2_penalty_answer
623
599
  ])
624
600
 
625
- answers.append(self.answers["total_loss"])
601
+ answers.append(total_loss_answer)
626
602
  table_data.append([
627
603
  ca.Paragraph(["Total loss: ", ca.Equation(r"L_{total} = L_{base} + L_{reg}", inline=True)]),
628
- self.answers["total_loss"]
604
+ total_loss_answer
629
605
  ])
630
606
 
631
- answers.append(self.answers["grad_total_w0"])
607
+ answers.append(grad_total_w0_answer)
632
608
  table_data.append([
633
609
  ca.Paragraph(["Gradient: ", ca.Equation(r"\frac{\partial L_{total}}{\partial w_0}", inline=True)]),
634
- self.answers["grad_total_w0"]
610
+ grad_total_w0_answer
635
611
  ])
636
612
 
637
613
  body.add_element(ca.Table(data=table_data))
638
614
 
639
615
  return body, answers
640
616
 
641
- def get_body(self, **kwargs) -> ca.Section:
642
- """Build question body (backward compatible interface)."""
643
- body, _ = self._get_body(**kwargs)
644
- return body
645
-
646
- def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
617
+ def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
647
618
  """Build question explanation."""
648
619
  explanation = ca.Section()
649
620
 
@@ -735,11 +706,6 @@ class RegularizationCalculationQuestion(Question):
735
706
 
736
707
  return explanation, []
737
708
 
738
- def get_explanation(self, **kwargs) -> ca.Section:
739
- """Build question explanation (backward compatible interface)."""
740
- explanation, _ = self._get_explanation(**kwargs)
741
- return explanation
742
-
743
709
 
744
710
  @QuestionRegistry.register()
745
711
  class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin):
@@ -760,12 +726,16 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
760
726
  self.num_variables = kwargs.get("num_variables", 2)
761
727
  self.show_vanilla_sgd = kwargs.get("show_vanilla_sgd", True)
762
728
 
763
- def refresh(self, rng_seed=None, *args, **kwargs):
764
- super().refresh(rng_seed=rng_seed, *args, **kwargs)
729
+ def _build_context(self, *, rng_seed=None, **kwargs):
730
+ if "num_variables" in kwargs:
731
+ self.num_variables = kwargs.get("num_variables", self.num_variables)
732
+ if "show_vanilla_sgd" in kwargs:
733
+ self.show_vanilla_sgd = kwargs.get("show_vanilla_sgd", self.show_vanilla_sgd)
734
+
735
+ self.rng.seed(rng_seed)
765
736
 
766
737
  # Generate well-conditioned quadratic function
767
- self.variables, self.function, self.gradient_function, self.equation = \
768
- generate_function(self.rng, self.num_variables, max_degree=2, use_quadratic=True)
738
+ self.variables, self.function, self.gradient_function, self.equation = generate_function(self.rng, self.num_variables, max_degree=2, use_quadratic=True)
769
739
 
770
740
  # Generate current weights (small integers)
771
741
  self.current_weights = [
@@ -809,24 +779,11 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
809
779
  for w, grad in zip(self.current_weights, self.gradients)
810
780
  ]
811
781
 
812
- # Create answers
813
- self._create_answers()
782
+ context = dict(kwargs)
783
+ context["rng_seed"] = rng_seed
784
+ return context
814
785
 
815
- def _create_answers(self):
816
- """Create answer fields."""
817
- self.answers = {}
818
-
819
- # New velocity
820
- self.answers["velocity"] = ca.AnswerTypes.Vector(self.new_velocity, label="New velocity")
821
-
822
- # New weights with momentum
823
- self.answers["weights_momentum"] = ca.AnswerTypes.Vector(self.new_weights, label="Weights (momentum)")
824
-
825
- # Vanilla SGD weights for comparison
826
- if self.show_vanilla_sgd:
827
- self.answers["weights_sgd"] = ca.AnswerTypes.Vector(self.sgd_weights, label="Weights (vanilla SGD)")
828
-
829
- def _get_body(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
786
+ def _build_body(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
830
787
  """Build question body and collect answers."""
831
788
  body = ca.Section()
832
789
  answers = []
@@ -889,38 +846,38 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
889
846
  table_data = []
890
847
  table_data.append(["Update Type", "Formula", "Result"])
891
848
 
892
- answers.append(self.answers["velocity"])
849
+ velocity_answer = ca.AnswerTypes.Vector(self.new_velocity, label="New velocity")
850
+ weights_momentum_answer = ca.AnswerTypes.Vector(self.new_weights, label="Weights (momentum)")
851
+ weights_sgd_answer = None
852
+
853
+ answers.append(velocity_answer)
893
854
  table_data.append([
894
855
  "New velocity",
895
856
  ca.Equation(r"v' = \beta v + (1-\beta)\nabla f", inline=True),
896
- self.answers["velocity"]
857
+ velocity_answer
897
858
  ])
898
859
 
899
- answers.append(self.answers["weights_momentum"])
860
+ answers.append(weights_momentum_answer)
900
861
  table_data.append([
901
862
  "Weights (momentum)",
902
863
  ca.Equation(r"w' = w - \alpha v'", inline=True),
903
- self.answers["weights_momentum"]
864
+ weights_momentum_answer
904
865
  ])
905
866
 
906
867
  if self.show_vanilla_sgd:
907
- answers.append(self.answers["weights_sgd"])
868
+ weights_sgd_answer = ca.AnswerTypes.Vector(self.sgd_weights, label="Weights (vanilla SGD)")
869
+ answers.append(weights_sgd_answer)
908
870
  table_data.append([
909
871
  "Weights (vanilla SGD)",
910
872
  ca.Equation(r"w' = w - \alpha \nabla f", inline=True),
911
- self.answers["weights_sgd"]
873
+ weights_sgd_answer
912
874
  ])
913
875
 
914
876
  body.add_element(ca.Table(data=table_data))
915
877
 
916
878
  return body, answers
917
879
 
918
- def get_body(self, **kwargs) -> ca.Section:
919
- """Build question body (backward compatible interface)."""
920
- body, _ = self._get_body(**kwargs)
921
- return body
922
-
923
- def _get_explanation(self, **kwargs) -> Tuple[ca.Section, List[ca.Answer]]:
880
+ def _build_explanation(self, context) -> Tuple[ca.Section, List[ca.Answer]]:
924
881
  """Build question explanation."""
925
882
  explanation = ca.Section()
926
883
 
@@ -1002,8 +959,3 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
1002
959
  ]))
1003
960
 
1004
961
  return explanation, []
1005
-
1006
- def get_explanation(self, **kwargs) -> ca.Section:
1007
- """Build question explanation (backward compatible interface)."""
1008
- explanation, _ = self._get_explanation(**kwargs)
1009
- return explanation