QuizGenerator 0.4.2__py3-none-any.whl → 0.6.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.
- QuizGenerator/contentast.py +809 -117
- QuizGenerator/generate.py +219 -11
- QuizGenerator/misc.py +0 -556
- QuizGenerator/mixins.py +50 -29
- QuizGenerator/premade_questions/basic.py +3 -3
- QuizGenerator/premade_questions/cst334/languages.py +183 -175
- QuizGenerator/premade_questions/cst334/math_questions.py +81 -70
- QuizGenerator/premade_questions/cst334/memory_questions.py +262 -165
- QuizGenerator/premade_questions/cst334/persistence_questions.py +83 -60
- QuizGenerator/premade_questions/cst334/process.py +558 -79
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +39 -13
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +61 -36
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +29 -10
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +60 -43
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +173 -326
- QuizGenerator/premade_questions/cst463/models/attention.py +29 -14
- QuizGenerator/premade_questions/cst463/models/cnns.py +32 -20
- QuizGenerator/premade_questions/cst463/models/rnns.py +28 -15
- QuizGenerator/premade_questions/cst463/models/text.py +29 -15
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +38 -30
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +91 -111
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +128 -55
- QuizGenerator/question.py +114 -20
- QuizGenerator/quiz.py +81 -24
- QuizGenerator/regenerate.py +98 -29
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/RECORD +31 -33
- QuizGenerator/README.md +0 -5
- QuizGenerator/logging.yaml +0 -55
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.4.2.dist-info → quizgenerator-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,8 +4,8 @@ import logging
|
|
|
4
4
|
import math
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
|
-
from QuizGenerator.question import Question, QuestionRegistry
|
|
8
|
-
from QuizGenerator.contentast import ContentAST
|
|
7
|
+
from QuizGenerator.question import Question, QuestionRegistry
|
|
8
|
+
from QuizGenerator.contentast import ContentAST, AnswerTypes
|
|
9
9
|
from QuizGenerator.mixins import MathOperationQuestion
|
|
10
10
|
|
|
11
11
|
log = logging.getLogger(__name__)
|
|
@@ -22,10 +22,16 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
22
22
|
return [self.rng.randint(min_val, max_val) for _ in range(dimension)]
|
|
23
23
|
|
|
24
24
|
def _format_vector(self, vector):
|
|
25
|
-
"""
|
|
26
|
-
|
|
25
|
+
"""Return a ContentAST.Matrix element for the vector (format-independent).
|
|
26
|
+
|
|
27
|
+
The Matrix element will render appropriately for each output format:
|
|
28
|
+
- HTML: LaTeX bmatrix (for MathJax)
|
|
29
|
+
- Typst: mat() with square bracket delimiter
|
|
30
|
+
- LaTeX: bmatrix environment
|
|
31
|
+
"""
|
|
32
|
+
# Convert to column matrix format: [[v1], [v2], [v3]]
|
|
27
33
|
matrix_data = [[v] for v in vector]
|
|
28
|
-
return ContentAST.Matrix
|
|
34
|
+
return ContentAST.Matrix(data=matrix_data, bracket_type="b")
|
|
29
35
|
|
|
30
36
|
def _format_vector_inline(self, vector):
|
|
31
37
|
"""Format vector for inline display."""
|
|
@@ -61,10 +67,8 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
61
67
|
# Call parent refresh which will use our generate_operands method
|
|
62
68
|
super().refresh(*args, **kwargs)
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.vector_a = self.operand_a
|
|
67
|
-
self.vector_b = self.operand_b
|
|
70
|
+
self.vector_a = self.operand_a
|
|
71
|
+
self.vector_b = self.operand_b
|
|
68
72
|
|
|
69
73
|
def generate_subquestion_data(self):
|
|
70
74
|
"""Generate LaTeX content for each subpart of the question.
|
|
@@ -81,23 +85,6 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
81
85
|
subparts.append((vector_a_latex, self.get_operator(), vector_b_latex))
|
|
82
86
|
return subparts
|
|
83
87
|
|
|
84
|
-
def _add_single_question_answers(self, body):
|
|
85
|
-
"""Add Canvas-only answer fields for single questions."""
|
|
86
|
-
# Check if it's a scalar result (like dot product)
|
|
87
|
-
if hasattr(self, 'answers') and len(self.answers) == 1:
|
|
88
|
-
# Single scalar answer
|
|
89
|
-
answer_key = list(self.answers.keys())[0]
|
|
90
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Answer(answer=self.answers[answer_key])]))
|
|
91
|
-
else:
|
|
92
|
-
# Vector results (like addition) - show table
|
|
93
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
94
|
-
table_data = []
|
|
95
|
-
for i in range(self.dimension):
|
|
96
|
-
if f"result_{i}" in self.answers:
|
|
97
|
-
table_data.append([ContentAST.Answer(answer=self.answers[f"result_{i}"])])
|
|
98
|
-
if table_data:
|
|
99
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
100
|
-
|
|
101
88
|
# Abstract methods that subclasses must still implement
|
|
102
89
|
@abc.abstractmethod
|
|
103
90
|
def get_operator(self):
|
|
@@ -128,64 +115,58 @@ class VectorAddition(VectorMathQuestion):
|
|
|
128
115
|
return [vector_a[i] + vector_b[i] for i in range(len(vector_a))]
|
|
129
116
|
|
|
130
117
|
def create_subquestion_answers(self, subpart_index, result):
|
|
131
|
-
|
|
132
|
-
for j in range(len(result)):
|
|
133
|
-
self.answers[f"subpart_{letter}_{j}"] = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
118
|
+
raise NotImplementedError("Multipart not supported")
|
|
134
119
|
|
|
135
120
|
def create_single_answers(self, result):
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
self.answers["result"] = AnswerTypes.Vector(result)
|
|
122
|
+
|
|
123
|
+
def _get_body(self):
|
|
124
|
+
"""Build question body and collect answers."""
|
|
125
|
+
body = ContentAST.Section()
|
|
126
|
+
|
|
127
|
+
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
138
128
|
|
|
139
|
-
|
|
129
|
+
# Equation display using MathExpression for format-independent rendering
|
|
130
|
+
vector_a_elem = self._format_vector(self.vector_a)
|
|
131
|
+
vector_b_elem = self._format_vector(self.vector_b)
|
|
132
|
+
body.add_element(ContentAST.MathExpression([
|
|
133
|
+
vector_a_elem,
|
|
134
|
+
" + ",
|
|
135
|
+
vector_b_elem,
|
|
136
|
+
" = "
|
|
137
|
+
]))
|
|
138
|
+
|
|
139
|
+
# Canvas-only answer field - use stored answer for consistent UUID
|
|
140
|
+
answer = self.answers["result"]
|
|
141
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
142
|
+
body.add_element(ContentAST.OnlyHtml([answer]))
|
|
143
|
+
|
|
144
|
+
return body, list(self.answers.values())
|
|
145
|
+
|
|
146
|
+
def _get_explanation(self):
|
|
147
|
+
"""Build question explanation."""
|
|
140
148
|
explanation = ContentAST.Section()
|
|
141
149
|
|
|
142
150
|
explanation.add_element(ContentAST.Paragraph(["To add vectors, we add corresponding components:"]))
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# Add explanation for this subpart
|
|
159
|
-
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
160
|
-
explanation.add_element(
|
|
161
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
162
|
-
lhs="\\vec{a} + \\vec{b}",
|
|
163
|
-
rhs=[
|
|
164
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
165
|
-
f"\\begin{{bmatrix}} {addition_str} \\end{{bmatrix}}",
|
|
166
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
167
|
-
]
|
|
168
|
-
)
|
|
152
|
+
# Use LaTeX syntax for make_block_equation__multiline_equals
|
|
153
|
+
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
154
|
+
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
155
|
+
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
156
|
+
addition_str = r" \\ ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
|
|
157
|
+
|
|
158
|
+
explanation.add_element(
|
|
159
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
160
|
+
lhs=r"\vec{a} + \vec{b}",
|
|
161
|
+
rhs=[
|
|
162
|
+
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
163
|
+
f"\\begin{{bmatrix}} {addition_str} \\end{{bmatrix}}",
|
|
164
|
+
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
165
|
+
]
|
|
169
166
|
)
|
|
170
|
-
|
|
171
|
-
# Single part explanation (original behavior)
|
|
172
|
-
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
173
|
-
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
174
|
-
addition_str = r" \\ ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
|
|
175
|
-
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
176
|
-
|
|
177
|
-
explanation.add_element(
|
|
178
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
179
|
-
lhs="\\vec{a} + \\vec{b}",
|
|
180
|
-
rhs=[
|
|
181
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
182
|
-
f"\\begin{{bmatrix}} {addition_str} \\end{{bmatrix}}",
|
|
183
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
184
|
-
]
|
|
185
|
-
)
|
|
186
|
-
)
|
|
167
|
+
)
|
|
187
168
|
|
|
188
|
-
return explanation
|
|
169
|
+
return explanation, []
|
|
189
170
|
|
|
190
171
|
|
|
191
172
|
@QuestionRegistry.register()
|
|
@@ -197,155 +178,70 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
197
178
|
def _generate_scalar(self):
|
|
198
179
|
"""Generate a non-zero scalar for multiplication."""
|
|
199
180
|
scalar = self.rng.randint(-5, 5)
|
|
200
|
-
while scalar == 0:
|
|
181
|
+
while scalar == 0:
|
|
201
182
|
scalar = self.rng.randint(-5, 5)
|
|
202
183
|
return scalar
|
|
203
184
|
|
|
204
|
-
def generate_operands(self):
|
|
205
|
-
"""Override to generate scalar and vector."""
|
|
206
|
-
if not hasattr(self, 'dimension'):
|
|
207
|
-
self.dimension = self.rng.randint(self.MIN_DIMENSION, self.MAX_DIMENSION)
|
|
208
|
-
vector_a = self._generate_vector(self.dimension)
|
|
209
|
-
vector_b = self._generate_vector(self.dimension) # Not used, but kept for consistency
|
|
210
|
-
return vector_a, vector_b
|
|
211
|
-
|
|
212
185
|
def refresh(self, *args, **kwargs):
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Call Question.refresh() directly to get basic setup
|
|
218
|
-
Question.refresh(self, *args, **kwargs)
|
|
219
|
-
|
|
220
|
-
# Generate vector dimension
|
|
221
|
-
self.dimension = self.rng.randint(self.MIN_DIMENSION, self.MAX_DIMENSION)
|
|
222
|
-
|
|
223
|
-
# Clear any existing data
|
|
224
|
-
self.answers = {}
|
|
225
|
-
|
|
226
|
-
# Generate multiple subquestions with their own scalars
|
|
227
|
-
self.subquestion_data = []
|
|
228
|
-
for i in range(self.num_subquestions):
|
|
229
|
-
# Generate unique vectors and scalar for each subquestion
|
|
230
|
-
vector_a = self._generate_vector(self.dimension)
|
|
231
|
-
vector_b = self._generate_vector(self.dimension) # Not used, but kept for consistency
|
|
232
|
-
scalar = self._generate_scalar()
|
|
233
|
-
result = [scalar * component for component in vector_a]
|
|
234
|
-
|
|
235
|
-
self.subquestion_data.append({
|
|
236
|
-
'operand_a': vector_a,
|
|
237
|
-
'operand_b': vector_b,
|
|
238
|
-
'vector_a': vector_a,
|
|
239
|
-
'vector_b': vector_b,
|
|
240
|
-
'scalar': scalar,
|
|
241
|
-
'result': result
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
# Create answers for this subpart
|
|
245
|
-
self.create_subquestion_answers(i, result)
|
|
246
|
-
else:
|
|
247
|
-
# For single questions, generate scalar first
|
|
248
|
-
self.scalar = self._generate_scalar()
|
|
249
|
-
# Then call super() normally
|
|
250
|
-
super().refresh(*args, **kwargs)
|
|
186
|
+
# Generate scalar first, then call parent refresh
|
|
187
|
+
self.scalar = self._generate_scalar()
|
|
188
|
+
super().refresh(*args, **kwargs)
|
|
251
189
|
|
|
252
190
|
def get_operator(self):
|
|
253
191
|
return f"{self.scalar} \\cdot"
|
|
254
192
|
|
|
255
193
|
def calculate_single_result(self, vector_a, vector_b):
|
|
256
|
-
# For scalar multiplication, we only use vector_a and ignore vector_b
|
|
257
194
|
return [self.scalar * component for component in vector_a]
|
|
258
195
|
|
|
259
196
|
def create_subquestion_answers(self, subpart_index, result):
|
|
260
|
-
|
|
261
|
-
for j in range(len(result)):
|
|
262
|
-
self.answers[f"subpart_{letter}_{j}"] = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
197
|
+
raise NotImplementedError("Multipart not supported")
|
|
263
198
|
|
|
264
199
|
def create_single_answers(self, result):
|
|
265
|
-
|
|
266
|
-
self.answers[f"result_{i}"] = Answer.integer(f"result_{i}", result[i])
|
|
267
|
-
|
|
268
|
-
def generate_subquestion_data(self):
|
|
269
|
-
"""Override to handle scalar multiplication format."""
|
|
270
|
-
subparts = []
|
|
271
|
-
for data in self.subquestion_data:
|
|
272
|
-
vector_a_latex = self._format_vector(data['vector_a'])
|
|
273
|
-
# For scalar multiplication, we show scalar * vector as a single string
|
|
274
|
-
# Use the scalar from this specific subquestion's data
|
|
275
|
-
scalar = data['scalar']
|
|
276
|
-
subparts.append(f"{scalar} \\cdot {vector_a_latex}")
|
|
277
|
-
return subparts
|
|
200
|
+
self.answers["result"] = AnswerTypes.Vector(result)
|
|
278
201
|
|
|
279
|
-
def
|
|
202
|
+
def _get_body(self):
|
|
203
|
+
"""Build question body and collect answers."""
|
|
280
204
|
body = ContentAST.Section()
|
|
281
205
|
|
|
282
206
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
283
207
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
vector_a_latex = self._format_vector(self.vector_a)
|
|
292
|
-
body.add_element(ContentAST.Equation(f"{self.scalar} \\cdot {vector_a_latex} = ", inline=False))
|
|
208
|
+
# Equation display using MathExpression
|
|
209
|
+
vector_elem = self._format_vector(self.vector_a)
|
|
210
|
+
body.add_element(ContentAST.MathExpression([
|
|
211
|
+
f"{self.scalar} \\cdot ",
|
|
212
|
+
vector_elem,
|
|
213
|
+
" = "
|
|
214
|
+
]))
|
|
293
215
|
|
|
294
|
-
|
|
295
|
-
|
|
216
|
+
# Canvas-only answer field - use stored answer
|
|
217
|
+
answer = self.answers["result"]
|
|
218
|
+
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
219
|
+
body.add_element(ContentAST.OnlyHtml([answer]))
|
|
296
220
|
|
|
297
|
-
return body
|
|
221
|
+
return body, list(self.answers.values())
|
|
298
222
|
|
|
299
|
-
def
|
|
223
|
+
def _get_explanation(self):
|
|
224
|
+
"""Build question explanation."""
|
|
300
225
|
explanation = ContentAST.Section()
|
|
301
226
|
|
|
302
227
|
explanation.add_element(ContentAST.Paragraph(["To multiply a vector by a scalar, we multiply each component by the scalar:"]))
|
|
303
228
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
multiplication_str = r" \\ ".join([f"{scalar} \\cdot {v}" for v in vector_a])
|
|
317
|
-
result_str = r" \\ ".join([str(v) for v in result])
|
|
318
|
-
|
|
319
|
-
# Add explanation for this subpart
|
|
320
|
-
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
321
|
-
explanation.add_element(
|
|
322
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
323
|
-
lhs=f"{scalar} \\cdot \\vec{{v}}",
|
|
324
|
-
rhs=[
|
|
325
|
-
f"{scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
326
|
-
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
327
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
328
|
-
]
|
|
329
|
-
)
|
|
229
|
+
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
230
|
+
multiplication_str = r" \\ ".join([f"{self.scalar} \\cdot {v}" for v in self.vector_a])
|
|
231
|
+
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
232
|
+
|
|
233
|
+
explanation.add_element(
|
|
234
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
235
|
+
lhs=f"{self.scalar} \\cdot \\vec{{v}}",
|
|
236
|
+
rhs=[
|
|
237
|
+
f"{self.scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
238
|
+
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
239
|
+
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
240
|
+
]
|
|
330
241
|
)
|
|
331
|
-
|
|
332
|
-
# Single part explanation - use the correct attributes
|
|
333
|
-
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
334
|
-
multiplication_str = r" \\ ".join([f"{self.scalar} \\cdot {v}" for v in self.vector_a])
|
|
335
|
-
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
336
|
-
|
|
337
|
-
explanation.add_element(
|
|
338
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
339
|
-
lhs=f"{self.scalar} \\cdot \\vec{{v}}",
|
|
340
|
-
rhs=[
|
|
341
|
-
f"{self.scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
342
|
-
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
343
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
344
|
-
]
|
|
345
|
-
)
|
|
346
|
-
)
|
|
242
|
+
)
|
|
347
243
|
|
|
348
|
-
return explanation
|
|
244
|
+
return explanation, []
|
|
349
245
|
|
|
350
246
|
|
|
351
247
|
@QuestionRegistry.register()
|
|
@@ -361,66 +257,57 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
361
257
|
return sum(vector_a[i] * vector_b[i] for i in range(len(vector_a)))
|
|
362
258
|
|
|
363
259
|
def create_subquestion_answers(self, subpart_index, result):
|
|
364
|
-
|
|
365
|
-
self.answers[f"subpart_{letter}"] = Answer.integer(f"subpart_{letter}", result)
|
|
260
|
+
raise NotImplementedError("Multipart not supported")
|
|
366
261
|
|
|
367
262
|
def create_single_answers(self, result):
|
|
368
|
-
self.answers =
|
|
369
|
-
|
|
370
|
-
|
|
263
|
+
self.answers["dot_product"] = AnswerTypes.Int(result)
|
|
264
|
+
|
|
265
|
+
def _get_body(self):
|
|
266
|
+
"""Build question body and collect answers."""
|
|
267
|
+
body = ContentAST.Section()
|
|
268
|
+
|
|
269
|
+
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
270
|
+
|
|
271
|
+
# Equation display using MathExpression
|
|
272
|
+
vector_a_elem = self._format_vector(self.vector_a)
|
|
273
|
+
vector_b_elem = self._format_vector(self.vector_b)
|
|
274
|
+
body.add_element(ContentAST.MathExpression([
|
|
275
|
+
vector_a_elem,
|
|
276
|
+
" \\cdot ",
|
|
277
|
+
vector_b_elem,
|
|
278
|
+
" = "
|
|
279
|
+
]))
|
|
371
280
|
|
|
372
|
-
|
|
281
|
+
# Canvas-only answer field - use stored answer
|
|
282
|
+
answer = self.answers["dot_product"]
|
|
283
|
+
body.add_element(ContentAST.OnlyHtml([answer]))
|
|
284
|
+
|
|
285
|
+
return body, list(self.answers.values())
|
|
286
|
+
|
|
287
|
+
def _get_explanation(self):
|
|
288
|
+
"""Build question explanation."""
|
|
373
289
|
explanation = ContentAST.Section()
|
|
374
290
|
|
|
375
291
|
explanation.add_element(ContentAST.Paragraph(["The dot product is calculated by multiplying corresponding components and summing the results:"]))
|
|
376
292
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
# Add explanation for this subpart
|
|
392
|
-
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
393
|
-
explanation.add_element(
|
|
394
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
395
|
-
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
396
|
-
rhs=[
|
|
397
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
398
|
-
products_str,
|
|
399
|
-
calculation_str,
|
|
400
|
-
str(result)
|
|
401
|
-
]
|
|
402
|
-
)
|
|
403
|
-
)
|
|
404
|
-
else:
|
|
405
|
-
# Single part explanation (original behavior)
|
|
406
|
-
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
407
|
-
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
408
|
-
products_str = " + ".join([f"({self.vector_a[i]} \\cdot {self.vector_b[i]})" for i in range(self.dimension)])
|
|
409
|
-
calculation_str = " + ".join([str(self.vector_a[i] * self.vector_b[i]) for i in range(self.dimension)])
|
|
410
|
-
|
|
411
|
-
explanation.add_element(
|
|
412
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
413
|
-
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
414
|
-
rhs=[
|
|
415
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
416
|
-
products_str,
|
|
417
|
-
calculation_str,
|
|
418
|
-
str(self.result)
|
|
419
|
-
]
|
|
293
|
+
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
294
|
+
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
295
|
+
products_str = " + ".join([f"({self.vector_a[i]} \\cdot {self.vector_b[i]})" for i in range(self.dimension)])
|
|
296
|
+
calculation_str = " + ".join([str(self.vector_a[i] * self.vector_b[i]) for i in range(self.dimension)])
|
|
297
|
+
|
|
298
|
+
explanation.add_element(
|
|
299
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
300
|
+
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
301
|
+
rhs=[
|
|
302
|
+
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
303
|
+
products_str,
|
|
304
|
+
calculation_str,
|
|
305
|
+
str(self.result)
|
|
306
|
+
]
|
|
420
307
|
)
|
|
421
308
|
)
|
|
422
309
|
|
|
423
|
-
return explanation
|
|
310
|
+
return explanation, [] # Explanations don't have answers
|
|
424
311
|
|
|
425
312
|
|
|
426
313
|
@QuestionRegistry.register()
|
|
@@ -430,105 +317,65 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
430
317
|
MAX_DIMENSION = 3
|
|
431
318
|
|
|
432
319
|
def get_operator(self):
|
|
433
|
-
# Magnitude uses ||...|| notation, not an operator between vectors
|
|
434
320
|
return "||"
|
|
435
321
|
|
|
436
322
|
def calculate_single_result(self, vector_a, vector_b):
|
|
437
|
-
# For magnitude, we only use vector_a and ignore vector_b
|
|
438
323
|
magnitude_squared = sum(component ** 2 for component in vector_a)
|
|
439
324
|
return math.sqrt(magnitude_squared)
|
|
440
325
|
|
|
441
326
|
def create_subquestion_answers(self, subpart_index, result):
|
|
442
|
-
|
|
443
|
-
self.answers[f"subpart_{letter}"] = Answer.auto_float(f"subpart_{letter}", result)
|
|
327
|
+
raise NotImplementedError("Multipart not supported")
|
|
444
328
|
|
|
445
329
|
def create_single_answers(self, result):
|
|
446
|
-
self.answers =
|
|
447
|
-
"magnitude": Answer.auto_float("magnitude", result)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
def generate_subquestion_data(self):
|
|
451
|
-
"""Override to handle magnitude format ||vector||."""
|
|
452
|
-
subparts = []
|
|
453
|
-
for data in self.subquestion_data:
|
|
454
|
-
vector_a_latex = self._format_vector(data['vector_a'])
|
|
455
|
-
# For magnitude, we show ||vector|| as a single string
|
|
456
|
-
subparts.append(f"\\left\\|{vector_a_latex}\\right\\|")
|
|
457
|
-
return subparts
|
|
330
|
+
self.answers["magnitude"] = AnswerTypes.Float(result)
|
|
458
331
|
|
|
459
|
-
def
|
|
332
|
+
def _get_body(self):
|
|
333
|
+
"""Build question body and collect answers."""
|
|
460
334
|
body = ContentAST.Section()
|
|
461
335
|
|
|
462
336
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
463
337
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
vector_a_latex = self._format_vector(self.vector_a)
|
|
472
|
-
body.add_element(ContentAST.Equation(f"\\left\\|{vector_a_latex}\\right\\| = ", inline=False))
|
|
338
|
+
# Equation display using MathExpression
|
|
339
|
+
vector_elem = self._format_vector(self.vector_a)
|
|
340
|
+
body.add_element(ContentAST.MathExpression([
|
|
341
|
+
"||",
|
|
342
|
+
vector_elem,
|
|
343
|
+
"|| = "
|
|
344
|
+
]))
|
|
473
345
|
|
|
474
|
-
|
|
475
|
-
|
|
346
|
+
# Canvas-only answer field - use stored answer
|
|
347
|
+
answer = self.answers["magnitude"]
|
|
348
|
+
body.add_element(ContentAST.OnlyHtml([answer]))
|
|
476
349
|
|
|
477
|
-
return body
|
|
350
|
+
return body, list(self.answers.values())
|
|
478
351
|
|
|
479
|
-
def
|
|
352
|
+
def _get_explanation(self):
|
|
353
|
+
"""Build question explanation."""
|
|
480
354
|
explanation = ContentAST.Section()
|
|
481
355
|
|
|
482
356
|
explanation.add_element(ContentAST.Paragraph(["The magnitude of a vector is calculated using the formula:"]))
|
|
483
|
-
explanation.add_element(ContentAST.Equation(
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
rhs=[
|
|
505
|
-
f"\\left\\|\\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}\\right\\|",
|
|
506
|
-
f"\\sqrt{{{squares_str}}}",
|
|
507
|
-
f"\\sqrt{{{calculation_str}}}",
|
|
508
|
-
f"\\sqrt{{{sum_of_squares}}}",
|
|
509
|
-
result_formatted
|
|
510
|
-
]
|
|
511
|
-
)
|
|
357
|
+
explanation.add_element(ContentAST.Equation(
|
|
358
|
+
r"||\vec{v}|| = \sqrt{v_1^2 + v_2^2 + \cdots + v_n^2}", inline=False
|
|
359
|
+
))
|
|
360
|
+
|
|
361
|
+
# Use LaTeX syntax for make_block_equation__multiline_equals
|
|
362
|
+
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
363
|
+
squares_str = " + ".join([f"{v}^2" for v in self.vector_a])
|
|
364
|
+
calculation_str = " + ".join([str(v**2) for v in self.vector_a])
|
|
365
|
+
sum_of_squares = sum(component ** 2 for component in self.vector_a)
|
|
366
|
+
result_formatted = sorted(ContentAST.Answer.accepted_strings(self.result), key=lambda s: len(s))[0]
|
|
367
|
+
|
|
368
|
+
explanation.add_element(
|
|
369
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
370
|
+
lhs=r"||\vec{v}||",
|
|
371
|
+
rhs=[
|
|
372
|
+
f"\\left|\\left| \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}} \\right|\\right|",
|
|
373
|
+
f"\\sqrt{{{squares_str}}}",
|
|
374
|
+
f"\\sqrt{{{calculation_str}}}",
|
|
375
|
+
f"\\sqrt{{{sum_of_squares}}}",
|
|
376
|
+
result_formatted
|
|
377
|
+
]
|
|
512
378
|
)
|
|
513
|
-
|
|
514
|
-
# Single part explanation - use the correct attributes
|
|
515
|
-
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
516
|
-
squares_str = " + ".join([f"{v}^2" for v in self.vector_a])
|
|
517
|
-
calculation_str = " + ".join([str(v**2) for v in self.vector_a])
|
|
518
|
-
sum_of_squares = sum(component ** 2 for component in self.vector_a)
|
|
519
|
-
result_formatted = sorted(Answer.accepted_strings(self.result), key=lambda s: len(s))[0]
|
|
520
|
-
|
|
521
|
-
explanation.add_element(
|
|
522
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
523
|
-
lhs="\\left\\|\\vec{v}\\right\\|",
|
|
524
|
-
rhs=[
|
|
525
|
-
f"\\left\\|\\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}\\right\\|",
|
|
526
|
-
f"\\sqrt{{{squares_str}}}",
|
|
527
|
-
f"\\sqrt{{{calculation_str}}}",
|
|
528
|
-
f"\\sqrt{{{sum_of_squares}}}",
|
|
529
|
-
result_formatted
|
|
530
|
-
]
|
|
531
|
-
)
|
|
532
|
-
)
|
|
379
|
+
)
|
|
533
380
|
|
|
534
|
-
return explanation
|
|
381
|
+
return explanation, []
|