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.
- QuizGenerator/contentast.py +1056 -1231
- QuizGenerator/generate.py +174 -2
- QuizGenerator/misc.py +0 -6
- QuizGenerator/mixins.py +7 -8
- QuizGenerator/premade_questions/basic.py +3 -3
- QuizGenerator/premade_questions/cst334/languages.py +45 -51
- QuizGenerator/premade_questions/cst334/math_questions.py +9 -10
- QuizGenerator/premade_questions/cst334/memory_questions.py +39 -56
- QuizGenerator/premade_questions/cst334/persistence_questions.py +12 -27
- QuizGenerator/premade_questions/cst334/process.py +11 -22
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +11 -11
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +7 -7
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +6 -6
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +15 -19
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -442
- QuizGenerator/premade_questions/cst463/models/attention.py +7 -8
- QuizGenerator/premade_questions/cst463/models/cnns.py +6 -7
- QuizGenerator/premade_questions/cst463/models/rnns.py +6 -6
- QuizGenerator/premade_questions/cst463/models/text.py +7 -8
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +5 -9
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +22 -22
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +25 -25
- QuizGenerator/question.py +13 -14
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/METADATA +1 -1
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/RECORD +29 -29
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.5.1.dist-info → quizgenerator-0.6.1.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__)
|
|
@@ -67,10 +67,8 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
67
67
|
# Call parent refresh which will use our generate_operands method
|
|
68
68
|
super().refresh(*args, **kwargs)
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.vector_a = self.operand_a
|
|
73
|
-
self.vector_b = self.operand_b
|
|
70
|
+
self.vector_a = self.operand_a
|
|
71
|
+
self.vector_b = self.operand_b
|
|
74
72
|
|
|
75
73
|
def generate_subquestion_data(self):
|
|
76
74
|
"""Generate LaTeX content for each subpart of the question.
|
|
@@ -117,121 +115,58 @@ class VectorAddition(VectorMathQuestion):
|
|
|
117
115
|
return [vector_a[i] + vector_b[i] for i in range(len(vector_a))]
|
|
118
116
|
|
|
119
117
|
def create_subquestion_answers(self, subpart_index, result):
|
|
120
|
-
|
|
121
|
-
for j in range(len(result)):
|
|
122
|
-
self.answers[f"subpart_{letter}_{j}"] = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
118
|
+
raise NotImplementedError("Multipart not supported")
|
|
123
119
|
|
|
124
120
|
def create_single_answers(self, result):
|
|
125
|
-
|
|
126
|
-
for i in range(len(result)):
|
|
127
|
-
self.answers[f"result_{i}"] = Answer.integer(f"result_{i}", result[i])
|
|
128
|
-
|
|
129
|
-
def get_body(self):
|
|
130
|
-
"""Override parent get_body() to use our custom formatting."""
|
|
131
|
-
body, _ = self._get_body()
|
|
132
|
-
return body
|
|
121
|
+
self.answers["result"] = AnswerTypes.Vector(result)
|
|
133
122
|
|
|
134
123
|
def _get_body(self):
|
|
135
|
-
"""Build question body and collect answers
|
|
136
|
-
from typing import Tuple, List
|
|
137
|
-
|
|
124
|
+
"""Build question body and collect answers."""
|
|
138
125
|
body = ContentAST.Section()
|
|
139
|
-
answers = []
|
|
140
126
|
|
|
141
127
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
142
128
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
vector_b_elem = self._format_vector(self.vector_b)
|
|
160
|
-
body.add_element(ContentAST.MathExpression([
|
|
161
|
-
vector_a_elem,
|
|
162
|
-
" + ",
|
|
163
|
-
vector_b_elem,
|
|
164
|
-
" = "
|
|
165
|
-
]))
|
|
166
|
-
|
|
167
|
-
# Canvas-only answer fields (hidden from PDF)
|
|
168
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
169
|
-
table_data = []
|
|
170
|
-
for i in range(self.dimension):
|
|
171
|
-
ans = Answer.integer(f"result_{i}", self.result[i])
|
|
172
|
-
answers.append(ans)
|
|
173
|
-
table_data.append([ans])
|
|
174
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
175
|
-
|
|
176
|
-
return body, answers
|
|
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())
|
|
177
145
|
|
|
178
146
|
def _get_explanation(self):
|
|
179
|
-
"""Build question explanation
|
|
147
|
+
"""Build question explanation."""
|
|
180
148
|
explanation = ContentAST.Section()
|
|
181
149
|
|
|
182
150
|
explanation.add_element(ContentAST.Paragraph(["To add vectors, we add corresponding components:"]))
|
|
183
151
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
addition_elements = "; ".join([f"{vector_a[j]}+{vector_b[j]}" for j in range(self.dimension)])
|
|
199
|
-
addition_str = f"mat({addition_elements})"
|
|
200
|
-
|
|
201
|
-
# Add explanation for this subpart
|
|
202
|
-
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
203
|
-
explanation.add_element(
|
|
204
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
205
|
-
lhs="arrow(a) + arrow(b)", # Typst uses arrow() for vectors
|
|
206
|
-
rhs=[
|
|
207
|
-
f"{vector_a_str} + {vector_b_str}",
|
|
208
|
-
addition_str,
|
|
209
|
-
result_str
|
|
210
|
-
]
|
|
211
|
-
)
|
|
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
|
+
]
|
|
212
166
|
)
|
|
213
|
-
|
|
214
|
-
# Single part explanation - use Typst syntax
|
|
215
|
-
vector_a_str = self._format_vector(self.vector_a)
|
|
216
|
-
vector_b_str = self._format_vector(self.vector_b)
|
|
217
|
-
result_str = self._format_vector(self.result)
|
|
218
|
-
|
|
219
|
-
# Build addition step-by-step
|
|
220
|
-
addition_elements = "; ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
|
|
221
|
-
addition_str = f"mat({addition_elements})"
|
|
222
|
-
|
|
223
|
-
explanation.add_element(
|
|
224
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
225
|
-
lhs="arrow(a) + arrow(b)",
|
|
226
|
-
rhs=[
|
|
227
|
-
f"{vector_a_str} + {vector_b_str}",
|
|
228
|
-
addition_str,
|
|
229
|
-
result_str
|
|
230
|
-
]
|
|
231
|
-
)
|
|
232
|
-
)
|
|
167
|
+
)
|
|
233
168
|
|
|
234
|
-
return explanation, []
|
|
169
|
+
return explanation, []
|
|
235
170
|
|
|
236
171
|
|
|
237
172
|
@QuestionRegistry.register()
|
|
@@ -243,179 +178,70 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
243
178
|
def _generate_scalar(self):
|
|
244
179
|
"""Generate a non-zero scalar for multiplication."""
|
|
245
180
|
scalar = self.rng.randint(-5, 5)
|
|
246
|
-
while scalar == 0:
|
|
181
|
+
while scalar == 0:
|
|
247
182
|
scalar = self.rng.randint(-5, 5)
|
|
248
183
|
return scalar
|
|
249
184
|
|
|
250
|
-
def generate_operands(self):
|
|
251
|
-
"""Override to generate scalar and vector."""
|
|
252
|
-
if not hasattr(self, 'dimension'):
|
|
253
|
-
self.dimension = self.rng.randint(self.MIN_DIMENSION, self.MAX_DIMENSION)
|
|
254
|
-
vector_a = self._generate_vector(self.dimension)
|
|
255
|
-
vector_b = self._generate_vector(self.dimension) # Not used, but kept for consistency
|
|
256
|
-
return vector_a, vector_b
|
|
257
|
-
|
|
258
185
|
def refresh(self, *args, **kwargs):
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
# Call Question.refresh() directly to get basic setup
|
|
264
|
-
Question.refresh(self, *args, **kwargs)
|
|
265
|
-
|
|
266
|
-
# Generate vector dimension
|
|
267
|
-
self.dimension = self.rng.randint(self.MIN_DIMENSION, self.MAX_DIMENSION)
|
|
268
|
-
|
|
269
|
-
# Clear any existing data
|
|
270
|
-
self.answers = {}
|
|
271
|
-
|
|
272
|
-
# Generate multiple subquestions with their own scalars
|
|
273
|
-
self.subquestion_data = []
|
|
274
|
-
for i in range(self.num_subquestions):
|
|
275
|
-
# Generate unique vectors and scalar for each subquestion
|
|
276
|
-
vector_a = self._generate_vector(self.dimension)
|
|
277
|
-
vector_b = self._generate_vector(self.dimension) # Not used, but kept for consistency
|
|
278
|
-
scalar = self._generate_scalar()
|
|
279
|
-
result = [scalar * component for component in vector_a]
|
|
280
|
-
|
|
281
|
-
self.subquestion_data.append({
|
|
282
|
-
'operand_a': vector_a,
|
|
283
|
-
'operand_b': vector_b,
|
|
284
|
-
'vector_a': vector_a,
|
|
285
|
-
'vector_b': vector_b,
|
|
286
|
-
'scalar': scalar,
|
|
287
|
-
'result': result
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
# Create answers for this subpart
|
|
291
|
-
self.create_subquestion_answers(i, result)
|
|
292
|
-
else:
|
|
293
|
-
# For single questions, generate scalar first
|
|
294
|
-
self.scalar = self._generate_scalar()
|
|
295
|
-
# Then call super() normally
|
|
296
|
-
super().refresh(*args, **kwargs)
|
|
186
|
+
# Generate scalar first, then call parent refresh
|
|
187
|
+
self.scalar = self._generate_scalar()
|
|
188
|
+
super().refresh(*args, **kwargs)
|
|
297
189
|
|
|
298
190
|
def get_operator(self):
|
|
299
191
|
return f"{self.scalar} \\cdot"
|
|
300
192
|
|
|
301
193
|
def calculate_single_result(self, vector_a, vector_b):
|
|
302
|
-
# For scalar multiplication, we only use vector_a and ignore vector_b
|
|
303
194
|
return [self.scalar * component for component in vector_a]
|
|
304
195
|
|
|
305
196
|
def create_subquestion_answers(self, subpart_index, result):
|
|
306
|
-
|
|
307
|
-
for j in range(len(result)):
|
|
308
|
-
self.answers[f"subpart_{letter}_{j}"] = Answer.integer(f"subpart_{letter}_{j}", result[j])
|
|
197
|
+
raise NotImplementedError("Multipart not supported")
|
|
309
198
|
|
|
310
199
|
def create_single_answers(self, result):
|
|
311
|
-
|
|
312
|
-
self.answers[f"result_{i}"] = Answer.integer(f"result_{i}", result[i])
|
|
313
|
-
|
|
314
|
-
def generate_subquestion_data(self):
|
|
315
|
-
"""Override to handle scalar multiplication format."""
|
|
316
|
-
subparts = []
|
|
317
|
-
for data in self.subquestion_data:
|
|
318
|
-
vector_elem = self._format_vector(data['vector_a'])
|
|
319
|
-
scalar = data['scalar']
|
|
320
|
-
# Return MathExpression for format-independent rendering
|
|
321
|
-
subparts.append(ContentAST.MathExpression([
|
|
322
|
-
f"{scalar} \\cdot ",
|
|
323
|
-
vector_elem
|
|
324
|
-
], inline=True))
|
|
325
|
-
return subparts
|
|
326
|
-
|
|
327
|
-
def get_body(self):
|
|
328
|
-
"""Override parent get_body() to use our custom formatting."""
|
|
329
|
-
body, _ = self._get_body()
|
|
330
|
-
return body
|
|
200
|
+
self.answers["result"] = AnswerTypes.Vector(result)
|
|
331
201
|
|
|
332
202
|
def _get_body(self):
|
|
333
|
-
"""Build question body and collect answers
|
|
203
|
+
"""Build question body and collect answers."""
|
|
334
204
|
body = ContentAST.Section()
|
|
335
|
-
answers = []
|
|
336
205
|
|
|
337
206
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
338
207
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
# Single equation display using MathExpression
|
|
354
|
-
vector_elem = self._format_vector(self.vector_a)
|
|
355
|
-
body.add_element(ContentAST.MathExpression([
|
|
356
|
-
f"{self.scalar} \\cdot ",
|
|
357
|
-
vector_elem,
|
|
358
|
-
" = "
|
|
359
|
-
]))
|
|
360
|
-
|
|
361
|
-
# Canvas-only answer fields (hidden from PDF)
|
|
362
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Paragraph(["Enter your answer as a column vector:"])]))
|
|
363
|
-
table_data = []
|
|
364
|
-
for i in range(self.dimension):
|
|
365
|
-
ans = Answer.integer(f"result_{i}", self.result[i])
|
|
366
|
-
answers.append(ans)
|
|
367
|
-
table_data.append([ans])
|
|
368
|
-
body.add_element(ContentAST.OnlyHtml([ContentAST.Table(data=table_data, padding=True)]))
|
|
369
|
-
|
|
370
|
-
return body, answers
|
|
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
|
+
]))
|
|
215
|
+
|
|
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]))
|
|
220
|
+
|
|
221
|
+
return body, list(self.answers.values())
|
|
371
222
|
|
|
372
223
|
def _get_explanation(self):
|
|
373
|
-
"""Build question explanation
|
|
224
|
+
"""Build question explanation."""
|
|
374
225
|
explanation = ContentAST.Section()
|
|
375
226
|
|
|
376
227
|
explanation.add_element(ContentAST.Paragraph(["To multiply a vector by a scalar, we multiply each component by the scalar:"]))
|
|
377
228
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
explanation.add_element(ContentAST.Paragraph([f"Part ({letter}):"]))
|
|
392
|
-
explanation.add_element(
|
|
393
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
394
|
-
lhs=f"{scalar} \\cdot \\vec{{v}}",
|
|
395
|
-
rhs=[
|
|
396
|
-
f"{scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
397
|
-
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
398
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
399
|
-
]
|
|
400
|
-
)
|
|
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
|
+
]
|
|
401
241
|
)
|
|
402
|
-
|
|
403
|
-
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
404
|
-
multiplication_str = r" \\ ".join([f"{self.scalar} \\cdot {v}" for v in self.vector_a])
|
|
405
|
-
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
406
|
-
|
|
407
|
-
explanation.add_element(
|
|
408
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
409
|
-
lhs=f"{self.scalar} \\cdot \\vec{{v}}",
|
|
410
|
-
rhs=[
|
|
411
|
-
f"{self.scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
412
|
-
f"\\begin{{bmatrix}} {multiplication_str} \\end{{bmatrix}}",
|
|
413
|
-
f"\\begin{{bmatrix}} {result_str} \\end{{bmatrix}}"
|
|
414
|
-
]
|
|
415
|
-
)
|
|
416
|
-
)
|
|
242
|
+
)
|
|
417
243
|
|
|
418
|
-
return explanation, []
|
|
244
|
+
return explanation, []
|
|
419
245
|
|
|
420
246
|
|
|
421
247
|
@QuestionRegistry.register()
|
|
@@ -431,101 +257,53 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
431
257
|
return sum(vector_a[i] * vector_b[i] for i in range(len(vector_a)))
|
|
432
258
|
|
|
433
259
|
def create_subquestion_answers(self, subpart_index, result):
|
|
434
|
-
|
|
435
|
-
self.answers[f"subpart_{letter}"] = Answer.integer(f"subpart_{letter}", result)
|
|
260
|
+
raise NotImplementedError("Multipart not supported")
|
|
436
261
|
|
|
437
262
|
def create_single_answers(self, result):
|
|
438
|
-
self.answers =
|
|
439
|
-
"dot_product": Answer.integer("dot_product", result)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
def get_body(self):
|
|
443
|
-
"""Override parent get_body() to use our custom formatting."""
|
|
444
|
-
body, _ = self._get_body()
|
|
445
|
-
return body
|
|
263
|
+
self.answers["dot_product"] = AnswerTypes.Int(result)
|
|
446
264
|
|
|
447
265
|
def _get_body(self):
|
|
448
|
-
"""Build question body and collect answers
|
|
266
|
+
"""Build question body and collect answers."""
|
|
449
267
|
body = ContentAST.Section()
|
|
450
|
-
answers = []
|
|
451
268
|
|
|
452
269
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
453
270
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
vector_b_elem = self._format_vector(self.vector_b)
|
|
470
|
-
body.add_element(ContentAST.MathExpression([
|
|
471
|
-
vector_a_elem,
|
|
472
|
-
" \\cdot ",
|
|
473
|
-
vector_b_elem,
|
|
474
|
-
" = "
|
|
475
|
-
]))
|
|
476
|
-
|
|
477
|
-
# Canvas-only answer field (single scalar result)
|
|
478
|
-
ans = Answer.integer("dot_product", self.result)
|
|
479
|
-
answers.append(ans)
|
|
480
|
-
body.add_element(ContentAST.OnlyHtml([ans]))
|
|
481
|
-
|
|
482
|
-
return body, answers
|
|
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
|
+
]))
|
|
280
|
+
|
|
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())
|
|
483
286
|
|
|
484
287
|
def _get_explanation(self):
|
|
485
|
-
"""Build question explanation
|
|
288
|
+
"""Build question explanation."""
|
|
486
289
|
explanation = ContentAST.Section()
|
|
487
290
|
|
|
488
291
|
explanation.add_element(ContentAST.Paragraph(["The dot product is calculated by multiplying corresponding components and summing the results:"]))
|
|
489
292
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
505
|
-
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
506
|
-
rhs=[
|
|
507
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
508
|
-
products_str,
|
|
509
|
-
calculation_str,
|
|
510
|
-
str(result)
|
|
511
|
-
]
|
|
512
|
-
)
|
|
513
|
-
)
|
|
514
|
-
else:
|
|
515
|
-
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
516
|
-
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
517
|
-
products_str = " + ".join([f"({self.vector_a[i]} \\cdot {self.vector_b[i]})" for i in range(self.dimension)])
|
|
518
|
-
calculation_str = " + ".join([str(self.vector_a[i] * self.vector_b[i]) for i in range(self.dimension)])
|
|
519
|
-
|
|
520
|
-
explanation.add_element(
|
|
521
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
522
|
-
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
523
|
-
rhs=[
|
|
524
|
-
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
525
|
-
products_str,
|
|
526
|
-
calculation_str,
|
|
527
|
-
str(self.result)
|
|
528
|
-
]
|
|
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
|
+
]
|
|
529
307
|
)
|
|
530
308
|
)
|
|
531
309
|
|
|
@@ -539,136 +317,65 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
539
317
|
MAX_DIMENSION = 3
|
|
540
318
|
|
|
541
319
|
def get_operator(self):
|
|
542
|
-
# Magnitude uses ||...|| notation, not an operator between vectors
|
|
543
320
|
return "||"
|
|
544
321
|
|
|
545
322
|
def calculate_single_result(self, vector_a, vector_b):
|
|
546
|
-
# For magnitude, we only use vector_a and ignore vector_b
|
|
547
323
|
magnitude_squared = sum(component ** 2 for component in vector_a)
|
|
548
324
|
return math.sqrt(magnitude_squared)
|
|
549
325
|
|
|
550
326
|
def create_subquestion_answers(self, subpart_index, result):
|
|
551
|
-
|
|
552
|
-
letter = chr(ord('a') + subpart_index)
|
|
553
|
-
self.answers[f"subpart_{letter}"] = Answer.auto_float(f"subpart_{letter}", result)
|
|
327
|
+
raise NotImplementedError("Multipart not supported")
|
|
554
328
|
|
|
555
329
|
def create_single_answers(self, result):
|
|
556
|
-
|
|
557
|
-
self.answers = {
|
|
558
|
-
"magnitude": Answer.auto_float("magnitude", result)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
def generate_subquestion_data(self):
|
|
562
|
-
"""Override to handle magnitude format ||vector||.
|
|
563
|
-
|
|
564
|
-
Returns MathExpression elements for format-independent rendering.
|
|
565
|
-
"""
|
|
566
|
-
subparts = []
|
|
567
|
-
for data in self.subquestion_data:
|
|
568
|
-
vector_elem = self._format_vector(data['vector_a'])
|
|
569
|
-
# Create MathExpression for ||vector||
|
|
570
|
-
subparts.append(ContentAST.MathExpression([
|
|
571
|
-
"||",
|
|
572
|
-
vector_elem,
|
|
573
|
-
"||"
|
|
574
|
-
], inline=True))
|
|
575
|
-
return subparts
|
|
576
|
-
|
|
577
|
-
def get_body(self):
|
|
578
|
-
"""Override parent get_body() to use our custom formatting."""
|
|
579
|
-
body, _ = self._get_body()
|
|
580
|
-
return body
|
|
330
|
+
self.answers["magnitude"] = AnswerTypes.Float(result)
|
|
581
331
|
|
|
582
332
|
def _get_body(self):
|
|
583
|
-
"""Build question body and collect answers
|
|
584
|
-
from typing import Tuple, List
|
|
585
|
-
|
|
333
|
+
"""Build question body and collect answers."""
|
|
586
334
|
body = ContentAST.Section()
|
|
587
|
-
answers = []
|
|
588
335
|
|
|
589
336
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
590
337
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
# Single equation display using MathExpression for format-independent rendering
|
|
605
|
-
vector_elem = self._format_vector(self.vector_a)
|
|
606
|
-
body.add_element(ContentAST.MathExpression([
|
|
607
|
-
"||",
|
|
608
|
-
vector_elem,
|
|
609
|
-
"|| = "
|
|
610
|
-
]))
|
|
611
|
-
|
|
612
|
-
# Canvas-only answer field (hidden from PDF)
|
|
613
|
-
ans = Answer.auto_float("magnitude", self.result)
|
|
614
|
-
answers.append(ans)
|
|
615
|
-
body.add_element(ContentAST.OnlyHtml([ans]))
|
|
616
|
-
|
|
617
|
-
return body, answers
|
|
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
|
+
]))
|
|
345
|
+
|
|
346
|
+
# Canvas-only answer field - use stored answer
|
|
347
|
+
answer = self.answers["magnitude"]
|
|
348
|
+
body.add_element(ContentAST.OnlyHtml([answer]))
|
|
349
|
+
|
|
350
|
+
return body, list(self.answers.values())
|
|
618
351
|
|
|
619
352
|
def _get_explanation(self):
|
|
353
|
+
"""Build question explanation."""
|
|
620
354
|
explanation = ContentAST.Section()
|
|
621
355
|
|
|
622
356
|
explanation.add_element(ContentAST.Paragraph(["The magnitude of a vector is calculated using the formula:"]))
|
|
623
|
-
explanation.add_element(ContentAST.Equation(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
rhs=[
|
|
645
|
-
f"||{vector_str}||",
|
|
646
|
-
f"sqrt({squares_str})",
|
|
647
|
-
f"sqrt({calculation_str})",
|
|
648
|
-
f"sqrt({sum_of_squares})",
|
|
649
|
-
result_formatted
|
|
650
|
-
]
|
|
651
|
-
)
|
|
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
|
+
]
|
|
652
378
|
)
|
|
653
|
-
|
|
654
|
-
# Single part explanation - use Typst syntax
|
|
655
|
-
vector_str = self._format_vector(self.vector_a)
|
|
656
|
-
squares_str = " + ".join([f"{v}^2" for v in self.vector_a])
|
|
657
|
-
calculation_str = " + ".join([str(v**2) for v in self.vector_a])
|
|
658
|
-
sum_of_squares = sum(component ** 2 for component in self.vector_a)
|
|
659
|
-
result_formatted = sorted(Answer.accepted_strings(self.result), key=lambda s: len(s))[0]
|
|
660
|
-
|
|
661
|
-
explanation.add_element(
|
|
662
|
-
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
663
|
-
lhs="||arrow(v)||",
|
|
664
|
-
rhs=[
|
|
665
|
-
f"||{vector_str}||",
|
|
666
|
-
f"sqrt({squares_str})",
|
|
667
|
-
f"sqrt({calculation_str})",
|
|
668
|
-
f"sqrt({sum_of_squares})",
|
|
669
|
-
result_formatted
|
|
670
|
-
]
|
|
671
|
-
)
|
|
672
|
-
)
|
|
379
|
+
)
|
|
673
380
|
|
|
674
|
-
return explanation, []
|
|
381
|
+
return explanation, []
|