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
@@ -5,9 +5,8 @@ import keras
5
5
  import numpy as np
6
6
  from typing import List, Tuple
7
7
 
8
- from QuizGenerator.misc import MatrixAnswer
9
- from QuizGenerator.question import Question, QuestionRegistry, Answer
10
- from QuizGenerator.contentast import ContentAST
8
+ from QuizGenerator.question import Question, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
11
10
  from QuizGenerator.constants import MathRanges
12
11
  from QuizGenerator.mixins import TableQuestionMixin
13
12
 
@@ -62,12 +61,12 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
62
61
  ## Answers:
63
62
  # Q, K, V, output, weights
64
63
 
65
- self.answers["weights"] = MatrixAnswer("weights", self.weights, label="Weights")
66
- self.answers["output"] = MatrixAnswer("output", self.output, label="Output")
64
+ self.answers["weights"] = AnswerTypes.Matrix(self.weights, label="Weights")
65
+ self.answers["output"] = AnswerTypes.Matrix(self.output, label="Output")
67
66
 
68
67
  return True
69
68
 
70
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
69
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
71
70
  """Build question body and collect answers."""
72
71
  body = ContentAST.Section()
73
72
  answers = []
@@ -101,10 +100,10 @@ class AttentionForwardPass(MatrixQuestion, TableQuestionMixin):
101
100
  body, _ = self._get_body(**kwargs)
102
101
  return body
103
102
 
104
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
103
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
105
104
  """Build question explanation."""
106
105
  explanation = ContentAST.Section()
107
- digits = Answer.DEFAULT_ROUNDING_DIGITS
106
+ digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
108
107
 
109
108
  explanation.add_element(
110
109
  ContentAST.Paragraph([
@@ -5,9 +5,8 @@ import keras
5
5
  import numpy as np
6
6
  from typing import List, Tuple
7
7
 
8
- from QuizGenerator.question import Question, QuestionRegistry, Answer
9
- from QuizGenerator.misc import MatrixAnswer
10
- from QuizGenerator.contentast import ContentAST
8
+ from QuizGenerator.question import Question, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
11
10
  from QuizGenerator.constants import MathRanges
12
11
  from .matrices import MatrixQuestion
13
12
 
@@ -66,13 +65,13 @@ class ConvolutionCalculation(MatrixQuestion):
66
65
  self.result = self.conv2d_multi_channel(self.image, self.kernel, stride=self.stride, padding=self.padding)
67
66
 
68
67
  self.answers = {
69
- f"result_{i}" : MatrixAnswer(f"result_{i}", self.result[:,:,i], label=f"Result of filter {i}")
68
+ f"result_{i}" : AnswerTypes.Matrix(self.result[:,:,i], label=f"Result of filter {i}")
70
69
  for i in range(self.result.shape[-1])
71
70
  }
72
71
 
73
72
  return True
74
73
 
75
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
74
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
76
75
  """Build question body and collect answers."""
77
76
  body = ContentAST.Section()
78
77
  answers = []
@@ -119,10 +118,10 @@ class ConvolutionCalculation(MatrixQuestion):
119
118
  body, _ = self._get_body(**kwargs)
120
119
  return body
121
120
 
122
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
121
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
123
122
  """Build question explanation."""
124
123
  explanation = ContentAST.Section()
125
- digits = Answer.DEFAULT_ROUNDING_DIGITS
124
+ digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
126
125
 
127
126
  explanation.add_element(
128
127
  ContentAST.Paragraph([
@@ -6,8 +6,8 @@ import numpy as np
6
6
  from typing import List, Tuple
7
7
 
8
8
  from .matrices import MatrixQuestion
9
- from QuizGenerator.question import Question, QuestionRegistry, Answer
10
- from QuizGenerator.contentast import ContentAST
9
+ from QuizGenerator.question import Question, QuestionRegistry
10
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
11
11
  from QuizGenerator.constants import MathRanges
12
12
  from QuizGenerator.mixins import TableQuestionMixin
13
13
 
@@ -64,11 +64,11 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
64
64
  ## Answers:
65
65
  # x_seq, W_xh, W_hh, b_h, h_0, h_states
66
66
 
67
- self.answers["output_sequence"] = Answer.matrix(key="output_sequence", value=self.h_states, label="Hidden states")
67
+ self.answers["output_sequence"] = AnswerTypes.Matrix(value=self.h_states, label="Hidden states")
68
68
 
69
69
  return True
70
70
 
71
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
71
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
72
72
  """Build question body and collect answers."""
73
73
  body = ContentAST.Section()
74
74
  answers = []
@@ -103,10 +103,10 @@ class RNNForwardPass(MatrixQuestion, TableQuestionMixin):
103
103
  body, _ = self._get_body(**kwargs)
104
104
  return body
105
105
 
106
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
106
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
107
107
  """Build question explanation."""
108
108
  explanation = ContentAST.Section()
109
- digits = Answer.DEFAULT_ROUNDING_DIGITS
109
+ digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
110
110
 
111
111
  explanation.add_element(
112
112
  ContentAST.Paragraph([
@@ -5,10 +5,9 @@ import keras
5
5
  import numpy as np
6
6
  from typing import List, Tuple
7
7
 
8
- from QuizGenerator.misc import MatrixAnswer
9
8
  from QuizGenerator.premade_questions.cst463.models.matrices import MatrixQuestion
10
- from QuizGenerator.question import Question, QuestionRegistry, Answer
11
- from QuizGenerator.contentast import ContentAST
9
+ from QuizGenerator.question import Question, QuestionRegistry
10
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
12
11
  from QuizGenerator.constants import MathRanges
13
12
  from QuizGenerator.mixins import TableQuestionMixin
14
13
 
@@ -60,15 +59,15 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
60
59
 
61
60
  ## Answers:
62
61
  # center_word, center_emb, context_words, context_embs, logits, probs
63
- self.answers["logits"] = Answer.vector_value(key="logits", value=self.logits, label="Logits")
62
+ self.answers["logits"] = AnswerTypes.Vector(self.logits, label="Logits")
64
63
  most_likely_idx = np.argmax(self.probs)
65
64
  most_likely_word = self.context_words[most_likely_idx]
66
- self.answers["center_word"] = Answer.string(key="center_word", value=most_likely_word, label="Most likely context word")
65
+ self.answers["center_word"] = AnswerTypes.String(most_likely_word, label="Most likely context word")
67
66
 
68
67
 
69
68
  return True
70
69
 
71
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
70
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
72
71
  """Build question body and collect answers."""
73
72
  body = ContentAST.Section()
74
73
  answers = []
@@ -101,10 +100,10 @@ class word2vec__skipgram(MatrixQuestion, TableQuestionMixin):
101
100
  body, _ = self._get_body(**kwargs)
102
101
  return body
103
102
 
104
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
103
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
105
104
  """Build question explanation."""
106
105
  explanation = ContentAST.Section()
107
- digits = Answer.DEFAULT_ROUNDING_DIGITS
106
+ digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
108
107
 
109
108
  explanation.add_element(
110
109
  ContentAST.Paragraph([
@@ -5,8 +5,8 @@ import keras
5
5
  import numpy as np
6
6
  from typing import List, Tuple
7
7
 
8
- from QuizGenerator.question import Question, QuestionRegistry, Answer
9
- from QuizGenerator.contentast import ContentAST
8
+ from QuizGenerator.question import Question, QuestionRegistry
9
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
10
10
  from QuizGenerator.constants import MathRanges
11
11
 
12
12
  log = logging.getLogger(__name__)
@@ -85,15 +85,11 @@ class WeightCounting(Question, abc.ABC):
85
85
  continue
86
86
 
87
87
  self.num_parameters = self.model.count_params()
88
- self.answers["num_parameters"] = Answer.integer(
89
- "num_parameters",
90
- self.num_parameters,
91
- label="Number of Parameters"
92
- )
88
+ self.answers["num_parameters"] = AnswerTypes.Int(self.num_parameters, label="Number of Parameters")
93
89
 
94
90
  return True
95
91
 
96
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
92
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
97
93
  """Build question body and collect answers."""
98
94
  body = ContentAST.Section()
99
95
  answers = []
@@ -127,7 +123,7 @@ class WeightCounting(Question, abc.ABC):
127
123
  body, _ = self._get_body(**kwargs)
128
124
  return body
129
125
 
130
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
126
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
131
127
  """Build question explanation."""
132
128
  explanation = ContentAST.Section()
133
129
 
@@ -12,8 +12,8 @@ from typing import List, Tuple, Dict, Any
12
12
  import matplotlib.pyplot as plt
13
13
  import matplotlib.patches as mpatches
14
14
 
15
- from QuizGenerator.contentast import ContentAST
16
- from QuizGenerator.question import Question, Answer, QuestionRegistry
15
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
16
+ from QuizGenerator.question import Question, QuestionRegistry
17
17
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
18
18
  from ..models.matrices import MatrixQuestion
19
19
 
@@ -566,12 +566,12 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
566
566
  # Hidden layer activations
567
567
  for i in range(self.num_hidden):
568
568
  key = f"h{i+1}"
569
- self.answers[key] = Answer.float_value(key, float(self.a1[i]), label=f"h_{i+1}")
569
+ self.answers[key] = AnswerTypes.Float(float(self.a1[i]), label=f"h_{i + 1}")
570
570
 
571
571
  # Output
572
- self.answers["y_pred"] = Answer.float_value("y_pred", float(self.a2[0]), label="ŷ")
572
+ self.answers["y_pred"] = AnswerTypes.Float(float(self.a2[0]), label="ŷ")
573
573
 
574
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
574
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
575
575
  """Build question body and collect answers."""
576
576
  body = ContentAST.Section()
577
577
  answers = []
@@ -614,7 +614,7 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
614
614
  body, _ = self._get_body(**kwargs)
615
615
  return body
616
616
 
617
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
617
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
618
618
  """Build question explanation."""
619
619
  explanation = ContentAST.Section()
620
620
 
@@ -743,14 +743,14 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
743
743
  # Gradient for W2 (hidden to output)
744
744
  for i in range(self.num_hidden):
745
745
  key = f"dL_dw2_{i}"
746
- self.answers[key] = Answer.auto_float(key, self._compute_gradient_W2(i), label=f"∂L/∂w_{i+3}")
746
+ self.answers[key] = AnswerTypes.Float(self._compute_gradient_W2(i), label=f"∂L/∂w_{i + 3}")
747
747
 
748
748
  # Gradient for W1 (input to hidden) - pick first hidden neuron
749
749
  for j in range(self.num_inputs):
750
750
  key = f"dL_dw1_0{j}"
751
- self.answers[key] = Answer.auto_float(key, self._compute_gradient_W1(0, j), label=f"∂L/∂w_1{j+1}")
751
+ self.answers[key] = AnswerTypes.Float(self._compute_gradient_W1(0, j), label=f"∂L/∂w_1{j + 1}")
752
752
 
753
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
753
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
754
754
  """Build question body and collect answers."""
755
755
  body = ContentAST.Section()
756
756
  answers = []
@@ -800,7 +800,7 @@ class BackpropGradientQuestion(SimpleNeuralNetworkBase):
800
800
  body, _ = self._get_body(**kwargs)
801
801
  return body
802
802
 
803
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
803
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
804
804
  """Build question explanation."""
805
805
  explanation = ContentAST.Section()
806
806
 
@@ -920,13 +920,13 @@ class EnsembleAveragingQuestion(Question):
920
920
 
921
921
  # Mean prediction
922
922
  mean_pred = np.mean(self.predictions)
923
- self.answers["mean"] = Answer.float_value("mean", float(mean_pred), label="Mean (average)")
923
+ self.answers["mean"] = AnswerTypes.Float(float(mean_pred), label="Mean (average)")
924
924
 
925
925
  # Median (optional, but useful)
926
926
  median_pred = np.median(self.predictions)
927
- self.answers["median"] = Answer.float_value("median", float(median_pred), label="Median")
927
+ self.answers["median"] = AnswerTypes.Float(float(median_pred), label="Median")
928
928
 
929
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
929
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
930
930
  """Build question body and collect answers."""
931
931
  body = ContentAST.Section()
932
932
  answers = []
@@ -961,7 +961,7 @@ class EnsembleAveragingQuestion(Question):
961
961
  body, _ = self._get_body(**kwargs)
962
962
  return body
963
963
 
964
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
964
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
965
965
  """Build question explanation."""
966
966
  explanation = ContentAST.Section()
967
967
 
@@ -1083,20 +1083,20 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
1083
1083
  self.answers = {}
1084
1084
 
1085
1085
  # Forward pass answers
1086
- self.answers["y_pred"] = Answer.float_value("y_pred", float(self.a2[0]), label="1. Forward Pass - Network output ŷ")
1086
+ self.answers["y_pred"] = AnswerTypes.Float(float(self.a2[0]), label="1. Forward Pass - Network output ŷ")
1087
1087
 
1088
1088
  # Loss answer
1089
- self.answers["loss"] = Answer.float_value("loss", float(self.loss), label="2. Loss")
1089
+ self.answers["loss"] = AnswerTypes.Float(float(self.loss), label="2. Loss")
1090
1090
 
1091
1091
  # Gradient answers (for key weights)
1092
- self.answers["grad_w3"] = Answer.auto_float("grad_w3", self._compute_gradient_W2(0), label="3. Gradient ∂L/∂w₃")
1093
- self.answers["grad_w11"] = Answer.auto_float("grad_w11", self._compute_gradient_W1(0, 0), label="4. Gradient ∂L/∂w₁₁")
1092
+ self.answers["grad_w3"] = AnswerTypes.Float(self._compute_gradient_W2(0), label="3. Gradient ∂L/∂w₃")
1093
+ self.answers["grad_w11"] = AnswerTypes.Float(self._compute_gradient_W1(0, 0), label="4. Gradient ∂L/∂w₁₁")
1094
1094
 
1095
1095
  # Updated weight answers
1096
- self.answers["new_w3"] = Answer.float_value("new_w3", float(self.new_W2[0, 0]), label="5. Updated w₃:")
1097
- self.answers["new_w11"] = Answer.float_value("new_w11", float(self.new_W1[0, 0]), label="6. Updated w₁₁:")
1096
+ self.answers["new_w3"] = AnswerTypes.Float(float(self.new_W2[0, 0]), label="5. Updated w₃:")
1097
+ self.answers["new_w11"] = AnswerTypes.Float(float(self.new_W1[0, 0]), label="6. Updated w₁₁:")
1098
1098
 
1099
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
1099
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
1100
1100
  """Build question body and collect answers."""
1101
1101
  body = ContentAST.Section()
1102
1102
  answers = []
@@ -1162,7 +1162,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
1162
1162
  body, _ = self._get_body(**kwargs)
1163
1163
  return body
1164
1164
 
1165
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
1165
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
1166
1166
  """Build question explanation."""
1167
1167
  explanation = ContentAST.Section()
1168
1168
 
@@ -8,8 +8,8 @@ import numpy as np
8
8
  import sympy as sp
9
9
  from typing import List, Tuple, Dict, Any
10
10
 
11
- from QuizGenerator.contentast import ContentAST
12
- from QuizGenerator.question import Question, Answer, QuestionRegistry
11
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
12
+ from QuizGenerator.question import Question, QuestionRegistry
13
13
  from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
14
14
 
15
15
  # Import gradient descent utilities
@@ -85,15 +85,15 @@ class ParameterCountingQuestion(Question):
85
85
  """Create answer fields."""
86
86
  self.answers = {}
87
87
 
88
- self.answers["total_weights"] = Answer.integer("total_weights", self.total_weights, label="Total weights")
88
+ self.answers["total_weights"] = AnswerTypes.Int(self.total_weights, label="Total weights")
89
89
 
90
90
  if self.include_biases:
91
- self.answers["total_biases"] = Answer.integer("total_biases", self.total_biases, label="Total biases")
92
- self.answers["total_params"] = Answer.integer("total_params", self.total_params, label="Total trainable parameters")
91
+ self.answers["total_biases"] = AnswerTypes.Int(self.total_biases, label="Total biases")
92
+ self.answers["total_params"] = AnswerTypes.Int(self.total_params, label="Total trainable parameters")
93
93
  else:
94
- self.answers["total_params"] = Answer.integer("total_params", self.total_params, label="Total trainable parameters")
94
+ self.answers["total_params"] = AnswerTypes.Int(self.total_params, label="Total trainable parameters")
95
95
 
96
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
96
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
97
97
  """Build question body and collect answers."""
98
98
  body = ContentAST.Section()
99
99
  answers = []
@@ -152,7 +152,7 @@ class ParameterCountingQuestion(Question):
152
152
  body, _ = self._get_body(**kwargs)
153
153
  return body
154
154
 
155
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
155
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
156
156
  """Build question explanation."""
157
157
  explanation = ContentAST.Section()
158
158
 
@@ -331,14 +331,14 @@ class ActivationFunctionComputationQuestion(Question):
331
331
 
332
332
  if self.activation == self.ACTIVATION_SOFTMAX:
333
333
  # Softmax: single vector answer
334
- self.answers["output"] = Answer.vector_value("output", self.output_vector, label="Output vector")
334
+ self.answers["output"] = AnswerTypes.Vector(self.output_vector, label="Output vector")
335
335
  else:
336
336
  # Element-wise: individual answers
337
337
  for i, output in enumerate(self.output_vector):
338
338
  key = f"output_{i}"
339
- self.answers[key] = Answer.float_value(key, float(output), label=f"Output for input {self.input_vector[i]:.1f}")
339
+ self.answers[key] = AnswerTypes.Float(float(output), label=f"Output for input {self.input_vector[i]:.1f}")
340
340
 
341
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
341
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
342
342
  """Build question body and collect answers."""
343
343
  body = ContentAST.Section()
344
344
  answers = []
@@ -399,7 +399,7 @@ class ActivationFunctionComputationQuestion(Question):
399
399
  body, _ = self._get_body(**kwargs)
400
400
  return body
401
401
 
402
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
402
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
403
403
  """Build question explanation."""
404
404
  explanation = ContentAST.Section()
405
405
 
@@ -545,13 +545,13 @@ class RegularizationCalculationQuestion(Question):
545
545
  """Create answer fields."""
546
546
  self.answers = {}
547
547
 
548
- self.answers["prediction"] = Answer.float_value("prediction", float(self.prediction), label="Prediction ŷ")
549
- self.answers["base_loss"] = Answer.float_value("base_loss", float(self.base_loss), label="Base MSE loss")
550
- self.answers["l2_penalty"] = Answer.float_value("l2_penalty", float(self.l2_penalty), label="L2 penalty")
551
- self.answers["total_loss"] = Answer.float_value("total_loss", float(self.total_loss), label="Total loss")
552
- self.answers["grad_total_w0"] = Answer.auto_float("grad_total_w0", float(self.grad_total_w0), label="Gradient ∂L/∂w₀")
548
+ self.answers["prediction"] = AnswerTypes.Float(float(self.prediction), label="Prediction ŷ")
549
+ self.answers["base_loss"] = AnswerTypes.Float(float(self.base_loss), label="Base MSE loss")
550
+ self.answers["l2_penalty"] = AnswerTypes.Float(float(self.l2_penalty), label="L2 penalty")
551
+ self.answers["total_loss"] = AnswerTypes.Float(float(self.total_loss), label="Total loss")
552
+ self.answers["grad_total_w0"] = AnswerTypes.Float(float(self.grad_total_w0), label="Gradient ∂L/∂w₀")
553
553
 
554
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
554
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
555
555
  """Build question body and collect answers."""
556
556
  body = ContentAST.Section()
557
557
  answers = []
@@ -643,7 +643,7 @@ class RegularizationCalculationQuestion(Question):
643
643
  body, _ = self._get_body(**kwargs)
644
644
  return body
645
645
 
646
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
646
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
647
647
  """Build question explanation."""
648
648
  explanation = ContentAST.Section()
649
649
 
@@ -817,16 +817,16 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
817
817
  self.answers = {}
818
818
 
819
819
  # New velocity
820
- self.answers["velocity"] = Answer.vector_value("velocity", self.new_velocity, label="New velocity")
820
+ self.answers["velocity"] = AnswerTypes.Vector(self.new_velocity, label="New velocity")
821
821
 
822
822
  # New weights with momentum
823
- self.answers["weights_momentum"] = Answer.vector_value("weights_momentum", self.new_weights, label="Weights (momentum)")
823
+ self.answers["weights_momentum"] = AnswerTypes.Vector(self.new_weights, label="Weights (momentum)")
824
824
 
825
825
  # Vanilla SGD weights for comparison
826
826
  if self.show_vanilla_sgd:
827
- self.answers["weights_sgd"] = Answer.vector_value("weights_sgd", self.sgd_weights, label="Weights (vanilla SGD)")
827
+ self.answers["weights_sgd"] = AnswerTypes.Vector(self.sgd_weights, label="Weights (vanilla SGD)")
828
828
 
829
- def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
829
+ def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
830
830
  """Build question body and collect answers."""
831
831
  body = ContentAST.Section()
832
832
  answers = []
@@ -920,7 +920,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
920
920
  body, _ = self._get_body(**kwargs)
921
921
  return body
922
922
 
923
- def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
923
+ def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[ContentAST.Answer]]:
924
924
  """Build question explanation."""
925
925
  explanation = ContentAST.Section()
926
926
 
@@ -944,7 +944,7 @@ class MomentumOptimizerQuestion(Question, TableQuestionMixin, BodyTemplatesMixin
944
944
  ))
945
945
 
946
946
  # Show calculation for each component
947
- digits = Answer.DEFAULT_ROUNDING_DIGITS
947
+ digits = ContentAST.Answer.DEFAULT_ROUNDING_DIGITS
948
948
  for i in range(self.num_variables):
949
949
  var_name = f"x_{i}"
950
950
  # Round all intermediate values to avoid floating point precision issues
QuizGenerator/question.py CHANGED
@@ -21,8 +21,7 @@ import yaml
21
21
  from typing import List, Dict, Any, Tuple, Optional
22
22
  import canvasapi.course, canvasapi.quiz
23
23
 
24
- from QuizGenerator.misc import Answer
25
- from QuizGenerator.contentast import ContentAST
24
+ from QuizGenerator.contentast import ContentAST, AnswerTypes
26
25
  from QuizGenerator.performance import timer, PerformanceTracker
27
26
 
28
27
  import logging
@@ -33,7 +32,7 @@ log = logging.getLogger(__name__)
33
32
  class QuestionComponents:
34
33
  """Bundle of question parts generated during construction."""
35
34
  body: ContentAST.Element
36
- answers: List[Answer]
35
+ answers: List[ContentAST.Answer]
37
36
  explanation: ContentAST.Element
38
37
 
39
38
 
@@ -352,8 +351,8 @@ class Question(abc.ABC):
352
351
  ensures consistent rendering across PDF/LaTeX and Canvas/HTML formats.
353
352
 
354
353
  Required Methods:
355
- - _get_body(): Return Tuple[ContentAST.Section, List[Answer]] with body and answers
356
- - _get_explanation(): Return Tuple[ContentAST.Section, List[Answer]] with explanation
354
+ - _get_body(): Return Tuple[ContentAST.Section, List[ContentAST.Answer]] with body and answers
355
+ - _get_explanation(): Return Tuple[ContentAST.Section, List[ContentAST.Answer]] with explanation
357
356
 
358
357
  Note: get_body() and get_explanation() are provided for backward compatibility
359
358
  and call the _get_* methods, returning just the first element of the tuple.
@@ -373,7 +372,7 @@ class Question(abc.ABC):
373
372
  body.add_element(ContentAST.Matrix(data=matrix_data, bracket_type="b"))
374
373
 
375
374
  # Answer extends ContentAST.Leaf - add directly to body
376
- ans = Answer.integer("result", 42, label="Result")
375
+ ans = ContentAST.Answer.integer("result", 42, label="Result")
377
376
  answers.append(ans)
378
377
  body.add_element(ans)
379
378
  return body, answers
@@ -474,7 +473,7 @@ class Question(abc.ABC):
474
473
  self.points_value = points_value
475
474
  self.topic = topic
476
475
  self.spacing = parse_spacing(kwargs.get("spacing", 0))
477
- self.answer_kind = Answer.AnswerKind.BLANK
476
+ self.answer_kind = ContentAST.Answer.CanvasAnswerKind.BLANK
478
477
 
479
478
  # Support for multi-part questions (defaults to 1 for normal questions)
480
479
  self.num_subquestions = kwargs.get("num_subquestions", 1)
@@ -600,7 +599,7 @@ class Question(abc.ABC):
600
599
  [ContentAST.Text("[Please reach out to your professor for clarification]")]
601
600
  )
602
601
 
603
- def _get_body(self) -> Tuple[ContentAST.Element, List[Answer]]:
602
+ def _get_body(self) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
604
603
  """
605
604
  Build question body and collect answers (new pattern).
606
605
  Questions should override this to return (body, answers) tuple.
@@ -612,7 +611,7 @@ class Question(abc.ABC):
612
611
  body = self.get_body()
613
612
  return body, []
614
613
 
615
- def _get_explanation(self) -> Tuple[ContentAST.Element, List[Answer]]:
614
+ def _get_explanation(self) -> Tuple[ContentAST.Element, List[ContentAST.Answer]]:
616
615
  """
617
616
  Build question explanation and collect answers (new pattern).
618
617
  Questions can override this to include answers in explanations.
@@ -646,7 +645,7 @@ class Question(abc.ABC):
646
645
  explanation=explanation
647
646
  )
648
647
 
649
- def get_answers(self, *args, **kwargs) -> Tuple[Answer.AnswerKind, List[Dict[str,Any]]]:
648
+ def get_answers(self, *args, **kwargs) -> Tuple[ContentAST.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
650
649
  """
651
650
  Return answers from cached components (new pattern) or self.answers dict (old pattern).
652
651
  """
@@ -664,7 +663,7 @@ class Question(abc.ABC):
664
663
  answers = self._components.answers
665
664
  if self.can_be_numerical():
666
665
  return (
667
- Answer.AnswerKind.NUMERICAL_QUESTION,
666
+ ContentAST.Answer.CanvasAnswerKind.NUMERICAL_QUESTION,
668
667
  list(itertools.chain(*[a.get_for_canvas(single_answer=True) for a in answers]))
669
668
  )
670
669
  return (
@@ -676,7 +675,7 @@ class Question(abc.ABC):
676
675
  if len(self.answers.values()) > 0:
677
676
  if self.can_be_numerical():
678
677
  return (
679
- Answer.AnswerKind.NUMERICAL_QUESTION,
678
+ ContentAST.Answer.CanvasAnswerKind.NUMERICAL_QUESTION,
680
679
  list(itertools.chain(*[a.get_for_canvas(single_answer=True) for a in self.answers.values()]))
681
680
  )
682
681
  return (
@@ -684,7 +683,7 @@ class Question(abc.ABC):
684
683
  list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
685
684
  )
686
685
 
687
- return (Answer.AnswerKind.ESSAY, [])
686
+ return (ContentAST.Answer.CanvasAnswerKind.ESSAY, [])
688
687
 
689
688
  def refresh(self, rng_seed=None, *args, **kwargs):
690
689
  """If it is necessary to regenerate aspects between usages, this is the time to do it.
@@ -751,7 +750,7 @@ class Question(abc.ABC):
751
750
 
752
751
  def can_be_numerical(self):
753
752
  if (len(self.answers.values()) == 1
754
- and list(self.answers.values())[0].variable_kind in [Answer.VariableKind.FLOAT, Answer.VariableKind.AUTOFLOAT]
753
+ and isinstance(list(self.answers.values())[0], AnswerTypes.Float)
755
754
  ):
756
755
  return True
757
756
  return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuizGenerator
3
- Version: 0.5.1
3
+ Version: 0.6.1
4
4
  Summary: Generate randomized quiz questions for Canvas LMS and PDF exams
5
5
  Project-URL: Homepage, https://github.com/OtterDen-Lab/QuizGenerator
6
6
  Project-URL: Documentation, https://github.com/OtterDen-Lab/QuizGenerator/tree/main/documentation