QuizGenerator 0.6.3__py3-none-any.whl → 0.7.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 +2191 -2193
- QuizGenerator/misc.py +1 -1
- QuizGenerator/mixins.py +64 -64
- QuizGenerator/premade_questions/basic.py +16 -16
- QuizGenerator/premade_questions/cst334/languages.py +26 -26
- QuizGenerator/premade_questions/cst334/math_questions.py +42 -42
- QuizGenerator/premade_questions/cst334/memory_questions.py +124 -124
- QuizGenerator/premade_questions/cst334/persistence_questions.py +48 -48
- QuizGenerator/premade_questions/cst334/process.py +38 -38
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +45 -45
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +34 -34
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +53 -53
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +65 -65
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +39 -39
- QuizGenerator/premade_questions/cst463/models/attention.py +36 -36
- QuizGenerator/premade_questions/cst463/models/cnns.py +26 -26
- QuizGenerator/premade_questions/cst463/models/rnns.py +36 -36
- QuizGenerator/premade_questions/cst463/models/text.py +32 -32
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +15 -15
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +124 -124
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +161 -161
- QuizGenerator/question.py +41 -41
- QuizGenerator/quiz.py +7 -7
- QuizGenerator/typst_utils.py +2 -2
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.6.3.dist-info → quizgenerator-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,7 +3,7 @@ import abc
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
6
|
-
|
|
6
|
+
import QuizGenerator.contentast as ca
|
|
7
7
|
from QuizGenerator.mixins import MathOperationQuestion
|
|
8
8
|
|
|
9
9
|
log = logging.getLogger(__name__)
|
|
@@ -13,15 +13,15 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
13
13
|
"""
|
|
14
14
|
Base class for matrix mathematics questions with multipart support.
|
|
15
15
|
|
|
16
|
-
NOTE: This class demonstrates proper
|
|
16
|
+
NOTE: This class demonstrates proper content AST usage patterns.
|
|
17
17
|
When implementing similar question types (vectors, equations, etc.),
|
|
18
18
|
follow these patterns for consistent formatting across output formats.
|
|
19
19
|
|
|
20
20
|
Key patterns demonstrated:
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
21
|
+
- ca.Matrix for mathematical matrices
|
|
22
|
+
- ca.Equation.make_block_equation__multiline_equals for step-by-step solutions
|
|
23
|
+
- ca.OnlyHtml for Canvas-specific content
|
|
24
|
+
- ca.Answer.integer for numerical answers
|
|
25
25
|
"""
|
|
26
26
|
def __init__(self, *args, **kwargs):
|
|
27
27
|
kwargs["topic"] = kwargs.get("topic", Question.Topic.MATH)
|
|
@@ -32,7 +32,7 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
32
32
|
return [[self.rng.randint(min_val, max_val) for _ in range(cols)] for _ in range(rows)]
|
|
33
33
|
|
|
34
34
|
def _matrix_to_table(self, matrix, prefix=""):
|
|
35
|
-
"""Convert a matrix to
|
|
35
|
+
"""Convert a matrix to content AST table format."""
|
|
36
36
|
return [[f"{prefix}{matrix[i][j]}" for j in range(len(matrix[0]))] for i in range(len(matrix))]
|
|
37
37
|
|
|
38
38
|
def _create_answer_table(self, rows, cols, answers_dict, answer_prefix="answer"):
|
|
@@ -51,7 +51,7 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
51
51
|
row.append(ans)
|
|
52
52
|
answers.append(ans)
|
|
53
53
|
table_data.append(row)
|
|
54
|
-
return
|
|
54
|
+
return ca.Table(data=table_data, padding=True), answers
|
|
55
55
|
|
|
56
56
|
# Implement MathOperationQuestion abstract methods
|
|
57
57
|
|
|
@@ -62,7 +62,7 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
62
62
|
|
|
63
63
|
def format_operand_latex(self, operand):
|
|
64
64
|
"""Format a matrix for LaTeX display."""
|
|
65
|
-
return
|
|
65
|
+
return ca.Matrix.to_latex(operand, "b")
|
|
66
66
|
|
|
67
67
|
def format_single_equation(self, operand_a, operand_b):
|
|
68
68
|
"""Format the equation for single questions."""
|
|
@@ -86,8 +86,8 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
86
86
|
cols_ans = self.answers["result_cols"]
|
|
87
87
|
answers.extend([rows_ans, cols_ans])
|
|
88
88
|
body.add_element(
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
ca.OnlyHtml([
|
|
90
|
+
ca.AnswerBlock([rows_ans, cols_ans])
|
|
91
91
|
])
|
|
92
92
|
)
|
|
93
93
|
|
|
@@ -98,8 +98,8 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
98
98
|
table, table_answers = self._create_answer_table(rows, cols, self.answers)
|
|
99
99
|
answers.extend(table_answers)
|
|
100
100
|
body.add_element(
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
ca.OnlyHtml([
|
|
102
|
+
ca.Paragraph(["Result matrix:"]),
|
|
103
103
|
table
|
|
104
104
|
])
|
|
105
105
|
)
|
|
@@ -108,8 +108,8 @@ class MatrixMathQuestion(MathOperationQuestion, Question):
|
|
|
108
108
|
table, table_answers = self._create_answer_table(self.max_dim, self.max_dim, self.answers)
|
|
109
109
|
answers.extend(table_answers)
|
|
110
110
|
body.add_element(
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
ca.OnlyHtml([
|
|
112
|
+
ca.Paragraph(["Result matrix (use '-' if cell doesn't exist):"]),
|
|
113
113
|
table
|
|
114
114
|
])
|
|
115
115
|
)
|
|
@@ -169,7 +169,7 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
169
169
|
for i in range(rows):
|
|
170
170
|
for j in range(cols):
|
|
171
171
|
answer_key = f"answer_{i}_{j}"
|
|
172
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
172
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
173
173
|
else:
|
|
174
174
|
# For multipart questions, use subpart letter format
|
|
175
175
|
letter = chr(ord('a') + subpart_index)
|
|
@@ -178,7 +178,7 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
178
178
|
for i in range(rows):
|
|
179
179
|
for j in range(cols):
|
|
180
180
|
answer_key = f"subpart_{letter}_{i}_{j}"
|
|
181
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
181
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
182
182
|
|
|
183
183
|
def refresh(self, *args, **kwargs):
|
|
184
184
|
"""Override refresh to set rows/cols for compatibility."""
|
|
@@ -190,11 +190,11 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
190
190
|
self.matrix_b = self.operand_b
|
|
191
191
|
# rows and cols should already be set by generate_operands
|
|
192
192
|
|
|
193
|
-
def get_explanation(self, **kwargs) ->
|
|
194
|
-
explanation =
|
|
193
|
+
def get_explanation(self, **kwargs) -> ca.Section:
|
|
194
|
+
explanation = ca.Section()
|
|
195
195
|
|
|
196
196
|
explanation.add_element(
|
|
197
|
-
|
|
197
|
+
ca.Paragraph([
|
|
198
198
|
"Matrix addition is performed element-wise. Each element in the result matrix "
|
|
199
199
|
"is the sum of the corresponding elements in the input matrices."
|
|
200
200
|
])
|
|
@@ -202,7 +202,7 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
202
202
|
|
|
203
203
|
if self.is_multipart():
|
|
204
204
|
# Handle multipart explanations
|
|
205
|
-
explanation.add_element(
|
|
205
|
+
explanation.add_element(ca.Paragraph(["Step-by-step calculation for each part:"]))
|
|
206
206
|
for i, data in enumerate(self.subquestion_data):
|
|
207
207
|
letter = chr(ord('a') + i)
|
|
208
208
|
matrix_a = data.get('matrix_a', data['operand_a'])
|
|
@@ -218,9 +218,9 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
218
218
|
result_str = r" \\ ".join([" & ".join([str(result[row][col]) for col in range(cols)]) for row in range(rows)])
|
|
219
219
|
|
|
220
220
|
# Add explanation for this subpart
|
|
221
|
-
explanation.add_element(
|
|
221
|
+
explanation.add_element(ca.Paragraph([f"Part ({letter}):"]))
|
|
222
222
|
explanation.add_element(
|
|
223
|
-
|
|
223
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
224
224
|
lhs="A + B",
|
|
225
225
|
rhs=[
|
|
226
226
|
f"\\begin{{bmatrix}} {matrix_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {matrix_b_str} \\end{{bmatrix}}",
|
|
@@ -231,7 +231,7 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
231
231
|
)
|
|
232
232
|
else:
|
|
233
233
|
# Single part explanation (original behavior)
|
|
234
|
-
explanation.add_element(
|
|
234
|
+
explanation.add_element(ca.Paragraph(["Step-by-step calculation:"]))
|
|
235
235
|
|
|
236
236
|
# Create properly formatted matrix strings
|
|
237
237
|
matrix_a_str = r" \\ ".join([" & ".join([str(self.matrix_a[i][j]) for j in range(self.cols)]) for i in range(self.rows)])
|
|
@@ -240,7 +240,7 @@ class MatrixAddition(MatrixMathQuestion):
|
|
|
240
240
|
result_str = r" \\ ".join([" & ".join([str(self.result[i][j]) for j in range(self.cols)]) for i in range(self.rows)])
|
|
241
241
|
|
|
242
242
|
explanation.add_element(
|
|
243
|
-
|
|
243
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
244
244
|
lhs="A + B",
|
|
245
245
|
rhs=[
|
|
246
246
|
f"\\begin{{bmatrix}} {matrix_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {matrix_b_str} \\end{{bmatrix}}",
|
|
@@ -299,7 +299,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
299
299
|
for i in range(rows):
|
|
300
300
|
for j in range(cols):
|
|
301
301
|
answer_key = f"answer_{i}_{j}"
|
|
302
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
302
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
303
303
|
else:
|
|
304
304
|
# For multipart questions, use subpart letter format
|
|
305
305
|
letter = chr(ord('a') + subpart_index)
|
|
@@ -308,7 +308,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
308
308
|
for i in range(rows):
|
|
309
309
|
for j in range(cols):
|
|
310
310
|
answer_key = f"subpart_{letter}_{i}_{j}"
|
|
311
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
311
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
312
312
|
|
|
313
313
|
def refresh(self, *args, **kwargs):
|
|
314
314
|
"""Override refresh to handle different scalars per subpart."""
|
|
@@ -355,7 +355,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
355
355
|
"""Override to handle scalar multiplication format."""
|
|
356
356
|
subparts = []
|
|
357
357
|
for data in self.subquestion_data:
|
|
358
|
-
matrix_latex =
|
|
358
|
+
matrix_latex = ca.Matrix.to_latex(data['matrix'], "b")
|
|
359
359
|
scalar = data['scalar']
|
|
360
360
|
# Return scalar * matrix as a single string
|
|
361
361
|
subparts.append(f"{scalar} \\cdot {matrix_latex}")
|
|
@@ -363,21 +363,21 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
363
363
|
|
|
364
364
|
def format_single_equation(self, operand_a, operand_b):
|
|
365
365
|
"""Format the equation for single questions."""
|
|
366
|
-
matrix_latex =
|
|
366
|
+
matrix_latex = ca.Matrix.to_latex(operand_a, "b")
|
|
367
367
|
return f"{self.scalar} \\cdot {matrix_latex}"
|
|
368
368
|
|
|
369
|
-
def get_explanation(self, **kwargs) ->
|
|
370
|
-
explanation =
|
|
369
|
+
def get_explanation(self, **kwargs) -> ca.Section:
|
|
370
|
+
explanation = ca.Section()
|
|
371
371
|
|
|
372
372
|
explanation.add_element(
|
|
373
|
-
|
|
373
|
+
ca.Paragraph([
|
|
374
374
|
"Scalar multiplication involves multiplying every element in the matrix by the scalar value."
|
|
375
375
|
])
|
|
376
376
|
)
|
|
377
377
|
|
|
378
378
|
if self.is_multipart():
|
|
379
379
|
# Handle multipart explanations
|
|
380
|
-
explanation.add_element(
|
|
380
|
+
explanation.add_element(ca.Paragraph(["Step-by-step calculation for each part:"]))
|
|
381
381
|
for i, data in enumerate(self.subquestion_data):
|
|
382
382
|
letter = chr(ord('a') + i)
|
|
383
383
|
matrix = data.get('matrix', data['operand_a'])
|
|
@@ -392,9 +392,9 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
392
392
|
result_str = r" \\ ".join([" & ".join([str(result[row][col]) for col in range(cols)]) for row in range(rows)])
|
|
393
393
|
|
|
394
394
|
# Add explanation for this subpart
|
|
395
|
-
explanation.add_element(
|
|
395
|
+
explanation.add_element(ca.Paragraph([f"Part ({letter}):"]))
|
|
396
396
|
explanation.add_element(
|
|
397
|
-
|
|
397
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
398
398
|
lhs=f"{scalar} \\cdot A",
|
|
399
399
|
rhs=[
|
|
400
400
|
f"{scalar} \\cdot \\begin{{bmatrix}} {matrix_str} \\end{{bmatrix}}",
|
|
@@ -405,7 +405,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
405
405
|
)
|
|
406
406
|
else:
|
|
407
407
|
# Single part explanation
|
|
408
|
-
explanation.add_element(
|
|
408
|
+
explanation.add_element(ca.Paragraph(["Step-by-step calculation:"]))
|
|
409
409
|
|
|
410
410
|
# Create properly formatted matrix strings
|
|
411
411
|
matrix_str = r" \\ ".join([" & ".join([str(self.matrix[i][j]) for j in range(self.cols)]) for i in range(self.rows)])
|
|
@@ -413,7 +413,7 @@ class MatrixScalarMultiplication(MatrixMathQuestion):
|
|
|
413
413
|
result_str = r" \\ ".join([" & ".join([str(self.result[i][j]) for j in range(self.cols)]) for i in range(self.rows)])
|
|
414
414
|
|
|
415
415
|
explanation.add_element(
|
|
416
|
-
|
|
416
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
417
417
|
lhs=f"{self.scalar} \\cdot A",
|
|
418
418
|
rhs=[
|
|
419
419
|
f"{self.scalar} \\cdot \\begin{{bmatrix}} {matrix_str} \\end{{bmatrix}}",
|
|
@@ -501,27 +501,27 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
501
501
|
# For single questions, use the old answer format
|
|
502
502
|
# Dimension answers
|
|
503
503
|
if result is not None:
|
|
504
|
-
self.answers["result_rows"] = AnswerTypes.Int(self.result_rows, label="Number of rows in result")
|
|
505
|
-
self.answers["result_cols"] = AnswerTypes.Int(self.result_cols, label="Number of columns in result")
|
|
504
|
+
self.answers["result_rows"] = ca.AnswerTypes.Int(self.result_rows, label="Number of rows in result")
|
|
505
|
+
self.answers["result_cols"] = ca.AnswerTypes.Int(self.result_cols, label="Number of columns in result")
|
|
506
506
|
|
|
507
507
|
# Matrix element answers
|
|
508
508
|
for i in range(self.max_dim):
|
|
509
509
|
for j in range(self.max_dim):
|
|
510
510
|
answer_key = f"answer_{i}_{j}"
|
|
511
511
|
if i < self.result_rows and j < self.result_cols:
|
|
512
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
512
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
513
513
|
else:
|
|
514
|
-
self.answers[answer_key] = AnswerTypes.String("-")
|
|
514
|
+
self.answers[answer_key] = ca.AnswerTypes.String("-")
|
|
515
515
|
else:
|
|
516
516
|
# Multiplication not possible
|
|
517
|
-
self.answers["result_rows"] = AnswerTypes.String("-", label="Number of rows in result")
|
|
518
|
-
self.answers["result_cols"] = AnswerTypes.String("-", label="Number of columns in result")
|
|
517
|
+
self.answers["result_rows"] = ca.AnswerTypes.String("-", label="Number of rows in result")
|
|
518
|
+
self.answers["result_cols"] = ca.AnswerTypes.String("-", label="Number of columns in result")
|
|
519
519
|
|
|
520
520
|
# All matrix elements are "-"
|
|
521
521
|
for i in range(self.max_dim):
|
|
522
522
|
for j in range(self.max_dim):
|
|
523
523
|
answer_key = f"answer_{i}_{j}"
|
|
524
|
-
self.answers[answer_key] = AnswerTypes.String("-")
|
|
524
|
+
self.answers[answer_key] = ca.AnswerTypes.String("-")
|
|
525
525
|
else:
|
|
526
526
|
# For multipart questions, use subpart letter format
|
|
527
527
|
letter = chr(ord('a') + subpart_index)
|
|
@@ -533,7 +533,7 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
533
533
|
for i in range(rows):
|
|
534
534
|
for j in range(cols):
|
|
535
535
|
answer_key = f"subpart_{letter}_{i}_{j}"
|
|
536
|
-
self.answers[answer_key] = AnswerTypes.Int(result[i][j])
|
|
536
|
+
self.answers[answer_key] = ca.AnswerTypes.Int(result[i][j])
|
|
537
537
|
|
|
538
538
|
def _add_single_question_answers(self, body):
|
|
539
539
|
"""Add Canvas-only answer fields for MatrixMultiplication with dash instruction.
|
|
@@ -549,8 +549,8 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
549
549
|
cols_ans = self.answers["result_cols"]
|
|
550
550
|
answers.extend([rows_ans, cols_ans])
|
|
551
551
|
body.add_element(
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
ca.OnlyHtml([
|
|
553
|
+
ca.AnswerBlock([rows_ans, cols_ans])
|
|
554
554
|
])
|
|
555
555
|
)
|
|
556
556
|
|
|
@@ -558,8 +558,8 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
558
558
|
table, table_answers = self._create_answer_table(self.max_dim, self.max_dim, self.answers)
|
|
559
559
|
answers.extend(table_answers)
|
|
560
560
|
body.add_element(
|
|
561
|
-
|
|
562
|
-
|
|
561
|
+
ca.OnlyHtml([
|
|
562
|
+
ca.Paragraph(["Result matrix (use '-' if cell doesn't exist):"]),
|
|
563
563
|
table
|
|
564
564
|
])
|
|
565
565
|
)
|
|
@@ -575,13 +575,13 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
575
575
|
self.matrix_a = self.operand_a
|
|
576
576
|
self.matrix_b = self.operand_b
|
|
577
577
|
|
|
578
|
-
def get_explanation(self, **kwargs) ->
|
|
579
|
-
explanation =
|
|
578
|
+
def get_explanation(self, **kwargs) -> ca.Section:
|
|
579
|
+
explanation = ca.Section()
|
|
580
580
|
|
|
581
581
|
if self.is_multipart():
|
|
582
582
|
# For multipart questions, provide simpler explanations
|
|
583
583
|
explanation.add_element(
|
|
584
|
-
|
|
584
|
+
ca.Paragraph([
|
|
585
585
|
"Matrix multiplication: Each element in the result is the dot product of "
|
|
586
586
|
"the corresponding row from the first matrix and column from the second matrix."
|
|
587
587
|
])
|
|
@@ -593,17 +593,17 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
593
593
|
matrix_b = data.get('matrix_b', data['operand_b'])
|
|
594
594
|
result = data['result']
|
|
595
595
|
|
|
596
|
-
explanation.add_element(
|
|
596
|
+
explanation.add_element(ca.Paragraph([f"Part ({letter}): Matrices multiplied successfully."]))
|
|
597
597
|
|
|
598
598
|
elif hasattr(self, 'multiplication_possible') and self.multiplication_possible:
|
|
599
599
|
# Single question with successful multiplication
|
|
600
|
-
explanation.add_element(
|
|
601
|
-
matrix_a_latex =
|
|
602
|
-
matrix_b_latex =
|
|
603
|
-
explanation.add_element(
|
|
600
|
+
explanation.add_element(ca.Paragraph(["Given matrices:"]))
|
|
601
|
+
matrix_a_latex = ca.Matrix.to_latex(self.matrix_a, "b")
|
|
602
|
+
matrix_b_latex = ca.Matrix.to_latex(self.matrix_b, "b")
|
|
603
|
+
explanation.add_element(ca.Equation(f"A = {matrix_a_latex}, \\quad B = {matrix_b_latex}"))
|
|
604
604
|
|
|
605
605
|
explanation.add_element(
|
|
606
|
-
|
|
606
|
+
ca.Paragraph([
|
|
607
607
|
f"Matrix multiplication is possible because the number of columns in Matrix A ({self.cols_a}) "
|
|
608
608
|
f"equals the number of rows in Matrix B ({self.rows_b}). "
|
|
609
609
|
f"The result is a {self.result_rows}×{self.result_cols} matrix."
|
|
@@ -611,10 +611,10 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
611
611
|
)
|
|
612
612
|
|
|
613
613
|
# Comprehensive matrix multiplication walkthrough
|
|
614
|
-
explanation.add_element(
|
|
614
|
+
explanation.add_element(ca.Paragraph(["Step-by-step calculation:"]))
|
|
615
615
|
|
|
616
616
|
# Show detailed multiplication process using row×column visualization
|
|
617
|
-
explanation.add_element(
|
|
617
|
+
explanation.add_element(ca.Paragraph(["Each element is calculated as the dot product of a row from Matrix A and a column from Matrix B:"]))
|
|
618
618
|
|
|
619
619
|
# Show calculation for first few elements with row×column visualization
|
|
620
620
|
for i in range(min(2, self.result_rows)):
|
|
@@ -631,18 +631,18 @@ class MatrixMultiplication(MatrixMathQuestion):
|
|
|
631
631
|
element_calc = " + ".join([f"{self.matrix_a[i][k]} \\cdot {self.matrix_b[k][j]}" for k in range(self.cols_a)])
|
|
632
632
|
|
|
633
633
|
explanation.add_element(
|
|
634
|
-
|
|
634
|
+
ca.Equation(f"({i+1},{j+1}): {row_latex} \\cdot {col_latex} = {element_calc} = {self.result[i][j]}")
|
|
635
635
|
)
|
|
636
636
|
|
|
637
|
-
explanation.add_element(
|
|
638
|
-
explanation.add_element(
|
|
637
|
+
explanation.add_element(ca.Paragraph(["Final result:"]))
|
|
638
|
+
explanation.add_element(ca.Matrix(data=self.result, bracket_type="b"))
|
|
639
639
|
else:
|
|
640
640
|
# Single question with failed multiplication
|
|
641
641
|
explanation.add_element(
|
|
642
|
-
|
|
642
|
+
ca.Paragraph([
|
|
643
643
|
f"Matrix multiplication is not possible because the number of columns in Matrix A ({getattr(self, 'cols_a', 'unknown')}) "
|
|
644
644
|
f"does not equal the number of rows in Matrix B ({getattr(self, 'rows_b', 'unknown')})."
|
|
645
645
|
])
|
|
646
646
|
)
|
|
647
647
|
|
|
648
|
-
return explanation
|
|
648
|
+
return explanation
|
|
@@ -5,7 +5,7 @@ import math
|
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
7
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
8
|
-
|
|
8
|
+
import QuizGenerator.contentast as ca
|
|
9
9
|
from QuizGenerator.mixins import MathOperationQuestion
|
|
10
10
|
|
|
11
11
|
log = logging.getLogger(__name__)
|
|
@@ -22,7 +22,7 @@ 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
|
-
"""Return a
|
|
25
|
+
"""Return a ca.Matrix element for the vector (format-independent).
|
|
26
26
|
|
|
27
27
|
The Matrix element will render appropriately for each output format:
|
|
28
28
|
- HTML: LaTeX bmatrix (for MathJax)
|
|
@@ -31,7 +31,7 @@ class VectorMathQuestion(MathOperationQuestion, Question):
|
|
|
31
31
|
"""
|
|
32
32
|
# Convert to column matrix format: [[v1], [v2], [v3]]
|
|
33
33
|
matrix_data = [[v] for v in vector]
|
|
34
|
-
return
|
|
34
|
+
return ca.Matrix(data=matrix_data, bracket_type="b")
|
|
35
35
|
|
|
36
36
|
def _format_vector_inline(self, vector):
|
|
37
37
|
"""Format vector for inline display."""
|
|
@@ -118,18 +118,18 @@ class VectorAddition(VectorMathQuestion):
|
|
|
118
118
|
raise NotImplementedError("Multipart not supported")
|
|
119
119
|
|
|
120
120
|
def create_single_answers(self, result):
|
|
121
|
-
self.answers["result"] = AnswerTypes.Vector(result)
|
|
121
|
+
self.answers["result"] = ca.AnswerTypes.Vector(result)
|
|
122
122
|
|
|
123
123
|
def _get_body(self):
|
|
124
124
|
"""Build question body and collect answers."""
|
|
125
|
-
body =
|
|
125
|
+
body = ca.Section()
|
|
126
126
|
|
|
127
|
-
body.add_element(
|
|
127
|
+
body.add_element(ca.Paragraph([self.get_intro_text()]))
|
|
128
128
|
|
|
129
129
|
# Equation display using MathExpression for format-independent rendering
|
|
130
130
|
vector_a_elem = self._format_vector(self.vector_a)
|
|
131
131
|
vector_b_elem = self._format_vector(self.vector_b)
|
|
132
|
-
body.add_element(
|
|
132
|
+
body.add_element(ca.MathExpression([
|
|
133
133
|
vector_a_elem,
|
|
134
134
|
" + ",
|
|
135
135
|
vector_b_elem,
|
|
@@ -138,16 +138,16 @@ class VectorAddition(VectorMathQuestion):
|
|
|
138
138
|
|
|
139
139
|
# Canvas-only answer field - use stored answer for consistent UUID
|
|
140
140
|
answer = self.answers["result"]
|
|
141
|
-
body.add_element(
|
|
142
|
-
body.add_element(
|
|
141
|
+
body.add_element(ca.OnlyHtml([ca.Paragraph(["Enter your answer as a column vector:"])]))
|
|
142
|
+
body.add_element(ca.OnlyHtml([answer]))
|
|
143
143
|
|
|
144
144
|
return body, list(self.answers.values())
|
|
145
145
|
|
|
146
146
|
def _get_explanation(self):
|
|
147
147
|
"""Build question explanation."""
|
|
148
|
-
explanation =
|
|
148
|
+
explanation = ca.Section()
|
|
149
149
|
|
|
150
|
-
explanation.add_element(
|
|
150
|
+
explanation.add_element(ca.Paragraph(["To add vectors, we add corresponding components:"]))
|
|
151
151
|
|
|
152
152
|
# Use LaTeX syntax for make_block_equation__multiline_equals
|
|
153
153
|
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
@@ -156,7 +156,7 @@ class VectorAddition(VectorMathQuestion):
|
|
|
156
156
|
addition_str = r" \\ ".join([f"{self.vector_a[i]}+{self.vector_b[i]}" for i in range(self.dimension)])
|
|
157
157
|
|
|
158
158
|
explanation.add_element(
|
|
159
|
-
|
|
159
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
160
160
|
lhs=r"\vec{a} + \vec{b}",
|
|
161
161
|
rhs=[
|
|
162
162
|
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} + \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
@@ -197,17 +197,17 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
197
197
|
raise NotImplementedError("Multipart not supported")
|
|
198
198
|
|
|
199
199
|
def create_single_answers(self, result):
|
|
200
|
-
self.answers["result"] = AnswerTypes.Vector(result)
|
|
200
|
+
self.answers["result"] = ca.AnswerTypes.Vector(result)
|
|
201
201
|
|
|
202
202
|
def _get_body(self):
|
|
203
203
|
"""Build question body and collect answers."""
|
|
204
|
-
body =
|
|
204
|
+
body = ca.Section()
|
|
205
205
|
|
|
206
|
-
body.add_element(
|
|
206
|
+
body.add_element(ca.Paragraph([self.get_intro_text()]))
|
|
207
207
|
|
|
208
208
|
# Equation display using MathExpression
|
|
209
209
|
vector_elem = self._format_vector(self.vector_a)
|
|
210
|
-
body.add_element(
|
|
210
|
+
body.add_element(ca.MathExpression([
|
|
211
211
|
f"{self.scalar} \\cdot ",
|
|
212
212
|
vector_elem,
|
|
213
213
|
" = "
|
|
@@ -215,23 +215,23 @@ class VectorScalarMultiplication(VectorMathQuestion):
|
|
|
215
215
|
|
|
216
216
|
# Canvas-only answer field - use stored answer
|
|
217
217
|
answer = self.answers["result"]
|
|
218
|
-
body.add_element(
|
|
219
|
-
body.add_element(
|
|
218
|
+
body.add_element(ca.OnlyHtml([ca.Paragraph(["Enter your answer as a column vector:"])]))
|
|
219
|
+
body.add_element(ca.OnlyHtml([answer]))
|
|
220
220
|
|
|
221
221
|
return body, list(self.answers.values())
|
|
222
222
|
|
|
223
223
|
def _get_explanation(self):
|
|
224
224
|
"""Build question explanation."""
|
|
225
|
-
explanation =
|
|
225
|
+
explanation = ca.Section()
|
|
226
226
|
|
|
227
|
-
explanation.add_element(
|
|
227
|
+
explanation.add_element(ca.Paragraph(["To multiply a vector by a scalar, we multiply each component by the scalar:"]))
|
|
228
228
|
|
|
229
229
|
vector_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
230
230
|
multiplication_str = r" \\ ".join([f"{self.scalar} \\cdot {v}" for v in self.vector_a])
|
|
231
231
|
result_str = r" \\ ".join([str(v) for v in self.result])
|
|
232
232
|
|
|
233
233
|
explanation.add_element(
|
|
234
|
-
|
|
234
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
235
235
|
lhs=f"{self.scalar} \\cdot \\vec{{v}}",
|
|
236
236
|
rhs=[
|
|
237
237
|
f"{self.scalar} \\cdot \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}}",
|
|
@@ -260,18 +260,18 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
260
260
|
raise NotImplementedError("Multipart not supported")
|
|
261
261
|
|
|
262
262
|
def create_single_answers(self, result):
|
|
263
|
-
self.answers["dot_product"] = AnswerTypes.Int(result)
|
|
263
|
+
self.answers["dot_product"] = ca.AnswerTypes.Int(result)
|
|
264
264
|
|
|
265
265
|
def _get_body(self):
|
|
266
266
|
"""Build question body and collect answers."""
|
|
267
|
-
body =
|
|
267
|
+
body = ca.Section()
|
|
268
268
|
|
|
269
|
-
body.add_element(
|
|
269
|
+
body.add_element(ca.Paragraph([self.get_intro_text()]))
|
|
270
270
|
|
|
271
271
|
# Equation display using MathExpression
|
|
272
272
|
vector_a_elem = self._format_vector(self.vector_a)
|
|
273
273
|
vector_b_elem = self._format_vector(self.vector_b)
|
|
274
|
-
body.add_element(
|
|
274
|
+
body.add_element(ca.MathExpression([
|
|
275
275
|
vector_a_elem,
|
|
276
276
|
" \\cdot ",
|
|
277
277
|
vector_b_elem,
|
|
@@ -280,15 +280,15 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
280
280
|
|
|
281
281
|
# Canvas-only answer field - use stored answer
|
|
282
282
|
answer = self.answers["dot_product"]
|
|
283
|
-
body.add_element(
|
|
283
|
+
body.add_element(ca.OnlyHtml([answer]))
|
|
284
284
|
|
|
285
285
|
return body, list(self.answers.values())
|
|
286
286
|
|
|
287
287
|
def _get_explanation(self):
|
|
288
288
|
"""Build question explanation."""
|
|
289
|
-
explanation =
|
|
289
|
+
explanation = ca.Section()
|
|
290
290
|
|
|
291
|
-
explanation.add_element(
|
|
291
|
+
explanation.add_element(ca.Paragraph(["The dot product is calculated by multiplying corresponding components and summing the results:"]))
|
|
292
292
|
|
|
293
293
|
vector_a_str = r" \\ ".join([str(v) for v in self.vector_a])
|
|
294
294
|
vector_b_str = r" \\ ".join([str(v) for v in self.vector_b])
|
|
@@ -296,7 +296,7 @@ class VectorDotProduct(VectorMathQuestion):
|
|
|
296
296
|
calculation_str = " + ".join([str(self.vector_a[i] * self.vector_b[i]) for i in range(self.dimension)])
|
|
297
297
|
|
|
298
298
|
explanation.add_element(
|
|
299
|
-
|
|
299
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
300
300
|
lhs="\\vec{a} \\cdot \\vec{b}",
|
|
301
301
|
rhs=[
|
|
302
302
|
f"\\begin{{bmatrix}} {vector_a_str} \\end{{bmatrix}} \\cdot \\begin{{bmatrix}} {vector_b_str} \\end{{bmatrix}}",
|
|
@@ -327,17 +327,17 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
327
327
|
raise NotImplementedError("Multipart not supported")
|
|
328
328
|
|
|
329
329
|
def create_single_answers(self, result):
|
|
330
|
-
self.answers["magnitude"] = AnswerTypes.Float(result)
|
|
330
|
+
self.answers["magnitude"] = ca.AnswerTypes.Float(result)
|
|
331
331
|
|
|
332
332
|
def _get_body(self):
|
|
333
333
|
"""Build question body and collect answers."""
|
|
334
|
-
body =
|
|
334
|
+
body = ca.Section()
|
|
335
335
|
|
|
336
|
-
body.add_element(
|
|
336
|
+
body.add_element(ca.Paragraph([self.get_intro_text()]))
|
|
337
337
|
|
|
338
338
|
# Equation display using MathExpression
|
|
339
339
|
vector_elem = self._format_vector(self.vector_a)
|
|
340
|
-
body.add_element(
|
|
340
|
+
body.add_element(ca.MathExpression([
|
|
341
341
|
"||",
|
|
342
342
|
vector_elem,
|
|
343
343
|
"|| = "
|
|
@@ -345,16 +345,16 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
345
345
|
|
|
346
346
|
# Canvas-only answer field - use stored answer
|
|
347
347
|
answer = self.answers["magnitude"]
|
|
348
|
-
body.add_element(
|
|
348
|
+
body.add_element(ca.OnlyHtml([answer]))
|
|
349
349
|
|
|
350
350
|
return body, list(self.answers.values())
|
|
351
351
|
|
|
352
352
|
def _get_explanation(self):
|
|
353
353
|
"""Build question explanation."""
|
|
354
|
-
explanation =
|
|
354
|
+
explanation = ca.Section()
|
|
355
355
|
|
|
356
|
-
explanation.add_element(
|
|
357
|
-
explanation.add_element(
|
|
356
|
+
explanation.add_element(ca.Paragraph(["The magnitude of a vector is calculated using the formula:"]))
|
|
357
|
+
explanation.add_element(ca.Equation(
|
|
358
358
|
r"||\vec{v}|| = \sqrt{v_1^2 + v_2^2 + \cdots + v_n^2}", inline=False
|
|
359
359
|
))
|
|
360
360
|
|
|
@@ -363,10 +363,10 @@ class VectorMagnitude(VectorMathQuestion):
|
|
|
363
363
|
squares_str = " + ".join([f"{v}^2" for v in self.vector_a])
|
|
364
364
|
calculation_str = " + ".join([str(v**2) for v in self.vector_a])
|
|
365
365
|
sum_of_squares = sum(component ** 2 for component in self.vector_a)
|
|
366
|
-
result_formatted = sorted(
|
|
366
|
+
result_formatted = sorted(ca.Answer.accepted_strings(self.result), key=lambda s: len(s))[0]
|
|
367
367
|
|
|
368
368
|
explanation.add_element(
|
|
369
|
-
|
|
369
|
+
ca.Equation.make_block_equation__multiline_equals(
|
|
370
370
|
lhs=r"||\vec{v}||",
|
|
371
371
|
rhs=[
|
|
372
372
|
f"\\left|\\left| \\begin{{bmatrix}} {vector_str} \\end{{bmatrix}} \\right|\\right|",
|