QuizGenerator 0.4.3__py3-none-any.whl → 0.5.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 +949 -80
- QuizGenerator/generate.py +44 -7
- QuizGenerator/misc.py +4 -554
- QuizGenerator/mixins.py +47 -25
- QuizGenerator/premade_questions/cst334/languages.py +139 -125
- QuizGenerator/premade_questions/cst334/math_questions.py +78 -66
- QuizGenerator/premade_questions/cst334/memory_questions.py +258 -144
- QuizGenerator/premade_questions/cst334/persistence_questions.py +71 -33
- QuizGenerator/premade_questions/cst334/process.py +51 -20
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +32 -6
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +59 -34
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +27 -8
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +53 -32
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +228 -88
- QuizGenerator/premade_questions/cst463/models/attention.py +26 -10
- QuizGenerator/premade_questions/cst463/models/cnns.py +32 -19
- QuizGenerator/premade_questions/cst463/models/rnns.py +25 -12
- QuizGenerator/premade_questions/cst463/models/text.py +26 -11
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +36 -22
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +89 -109
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +126 -53
- QuizGenerator/question.py +110 -15
- QuizGenerator/quiz.py +74 -23
- QuizGenerator/regenerate.py +98 -29
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/RECORD +29 -31
- QuizGenerator/README.md +0 -5
- QuizGenerator/logging.yaml +0 -55
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.4.3.dist-info → quizgenerator-0.5.0.dist-info}/licenses/LICENSE +0 -0
QuizGenerator/mixins.py
CHANGED
|
@@ -68,19 +68,20 @@ class TableQuestionMixin:
|
|
|
68
68
|
"""
|
|
69
69
|
answer_columns = answer_columns or []
|
|
70
70
|
|
|
71
|
-
def format_cell(row_data: Dict, column: str) -> Union[str,
|
|
71
|
+
def format_cell(row_data: Dict, column: str) -> Union[str, Answer]:
|
|
72
72
|
"""Format a cell based on whether it should be an answer or plain data"""
|
|
73
73
|
value = row_data.get(column, "")
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# If this column should contain answers and the value is an Answer object
|
|
76
|
+
# Answer extends ContentAST.Leaf, so it can be used directly
|
|
76
77
|
if column in answer_columns and isinstance(value, Answer):
|
|
77
|
-
return
|
|
78
|
+
return value
|
|
78
79
|
# If this column should contain answers but we have the answer key
|
|
79
80
|
elif column in answer_columns and isinstance(value, str) and hasattr(self, 'answers'):
|
|
80
81
|
answer_obj = self.answers.get(value)
|
|
81
82
|
if answer_obj:
|
|
82
|
-
return
|
|
83
|
-
|
|
83
|
+
return answer_obj
|
|
84
|
+
|
|
84
85
|
# Otherwise return as plain data
|
|
85
86
|
return str(value)
|
|
86
87
|
|
|
@@ -119,9 +120,9 @@ class TableQuestionMixin:
|
|
|
119
120
|
# Build data with parameters plus answer row
|
|
120
121
|
data = [[key, str(value)] for key, value in parameter_info.items()]
|
|
121
122
|
|
|
122
|
-
# Add answer row
|
|
123
|
+
# Add answer row - Answer extends ContentAST.Leaf so it can be used directly
|
|
123
124
|
if hasattr(self, 'answers') and answer_key in self.answers:
|
|
124
|
-
data.append([answer_label,
|
|
125
|
+
data.append([answer_label, self.answers[answer_key]])
|
|
125
126
|
else:
|
|
126
127
|
data.append([answer_label, f"[{answer_key}]"]) # Fallback
|
|
127
128
|
|
|
@@ -149,16 +150,17 @@ class TableQuestionMixin:
|
|
|
149
150
|
ContentAST.Table with multiple answer blanks
|
|
150
151
|
"""
|
|
151
152
|
|
|
152
|
-
def process_cell_value(value: Any) -> Union[str,
|
|
153
|
+
def process_cell_value(value: Any) -> Union[str, Answer]:
|
|
153
154
|
"""Convert cell values to appropriate display format"""
|
|
154
|
-
# If it's already an Answer object,
|
|
155
|
+
# If it's already an Answer object, use it directly
|
|
156
|
+
# Answer extends ContentAST.Leaf so it can be used in the AST
|
|
155
157
|
if isinstance(value, Answer):
|
|
156
|
-
return
|
|
158
|
+
return value
|
|
157
159
|
# If it's a string that looks like an answer key, try to resolve it
|
|
158
160
|
elif isinstance(value, str) and value.startswith("answer__") and hasattr(self, 'answers'):
|
|
159
161
|
answer_obj = self.answers.get(value)
|
|
160
162
|
if answer_obj:
|
|
161
|
-
return
|
|
163
|
+
return answer_obj
|
|
162
164
|
# Otherwise return as-is
|
|
163
165
|
return str(value)
|
|
164
166
|
|
|
@@ -493,37 +495,52 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
493
495
|
subparts.append((operand_a_latex, self.get_operator(), operand_b_latex))
|
|
494
496
|
return subparts
|
|
495
497
|
|
|
496
|
-
def
|
|
498
|
+
def _get_body(self):
|
|
499
|
+
"""Build question body and collect answers."""
|
|
497
500
|
body = ContentAST.Section()
|
|
498
|
-
|
|
501
|
+
answers = []
|
|
502
|
+
|
|
499
503
|
body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
|
|
500
|
-
|
|
504
|
+
|
|
501
505
|
if self.is_multipart():
|
|
502
506
|
# Use multipart formatting with repeated problem parts
|
|
503
507
|
subpart_data = self.generate_subquestion_data()
|
|
504
508
|
repeated_part = self.create_repeated_problem_part(subpart_data)
|
|
505
509
|
body.add_element(repeated_part)
|
|
510
|
+
# Collect answers from self.answers dict
|
|
511
|
+
answers = list(self.answers.values())
|
|
506
512
|
else:
|
|
507
513
|
# Single equation display
|
|
508
514
|
equation_latex = self.format_single_equation(self.operand_a, self.operand_b)
|
|
509
515
|
body.add_element(ContentAST.Equation(f"{equation_latex} = ", inline=False))
|
|
510
|
-
|
|
516
|
+
|
|
511
517
|
# Canvas-only answer fields (hidden from PDF)
|
|
512
|
-
self._add_single_question_answers(body)
|
|
513
|
-
|
|
518
|
+
single_answers = self._add_single_question_answers(body)
|
|
519
|
+
if single_answers:
|
|
520
|
+
answers.extend(single_answers)
|
|
521
|
+
|
|
522
|
+
return body, answers
|
|
523
|
+
|
|
524
|
+
def get_body(self):
|
|
525
|
+
"""Build question body (backward compatible interface)."""
|
|
526
|
+
body, _ = self._get_body()
|
|
514
527
|
return body
|
|
515
|
-
|
|
528
|
+
|
|
516
529
|
def _add_single_question_answers(self, body):
|
|
517
|
-
"""Add Canvas-only answer fields for single questions. Subclasses can override.
|
|
530
|
+
"""Add Canvas-only answer fields for single questions. Subclasses can override.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
List of Answer objects that were added to the body.
|
|
534
|
+
"""
|
|
518
535
|
# Default implementation - subclasses should override for specific answer formats
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def
|
|
536
|
+
return []
|
|
537
|
+
|
|
538
|
+
def _get_explanation(self):
|
|
522
539
|
"""Default explanation structure. Subclasses should override for specific explanations."""
|
|
523
540
|
explanation = ContentAST.Section()
|
|
524
|
-
|
|
541
|
+
|
|
525
542
|
explanation.add_element(ContentAST.Paragraph([self.get_explanation_intro()]))
|
|
526
|
-
|
|
543
|
+
|
|
527
544
|
if self.is_multipart():
|
|
528
545
|
# Handle multipart explanations
|
|
529
546
|
for i, data in enumerate(self.subquestion_data):
|
|
@@ -532,7 +549,12 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
532
549
|
else:
|
|
533
550
|
# Single part explanation
|
|
534
551
|
explanation.add_element(self.create_single_explanation())
|
|
535
|
-
|
|
552
|
+
|
|
553
|
+
return explanation, []
|
|
554
|
+
|
|
555
|
+
def get_explanation(self):
|
|
556
|
+
"""Build question explanation (backward compatible interface)."""
|
|
557
|
+
explanation, _ = self._get_explanation()
|
|
536
558
|
return explanation
|
|
537
559
|
|
|
538
560
|
def get_explanation_intro(self):
|
|
@@ -155,115 +155,115 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
155
155
|
|
|
156
156
|
super().__init__(*args, **kwargs)
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
# Store whether grammars are fixed (provided) or should be randomized
|
|
159
|
+
self.fixed_grammars = grammar_str_good is not None and grammar_str_bad is not None
|
|
160
|
+
if self.fixed_grammars:
|
|
159
161
|
self.grammar_str_good = grammar_str_good
|
|
160
162
|
self.grammar_str_bad = grammar_str_bad
|
|
161
163
|
self.include_spaces = kwargs.get("include_spaces", False)
|
|
162
164
|
self.MAX_LENGTH = kwargs.get("max_length", 30)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if which_grammar == 0:
|
|
167
|
-
# todo: make a few different kinds of grammars that could be picked
|
|
168
|
-
self.grammar_str_good = """
|
|
169
|
-
<expression> ::= <term> | <expression> + <term> | <expression> - <term>
|
|
170
|
-
<term> ::= <factor> | <term> * <factor> | <term> / <factor>
|
|
171
|
-
<factor> ::= <number>
|
|
172
|
-
<number> ::= <digit> | <number> <digit>
|
|
173
|
-
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
174
|
-
"""
|
|
175
|
-
# Adding in a plus to number
|
|
176
|
-
self.grammar_str_bad = """
|
|
177
|
-
<expression> ::= <term> | <expression> + <term> | <expression> - <term>
|
|
178
|
-
<term> ::= <factor> | <term> * <factor> | <term> / <factor>
|
|
179
|
-
<factor> ::= <number>
|
|
180
|
-
<number> ::= <digit> + | <digit> <number>
|
|
181
|
-
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
182
|
-
"""
|
|
183
|
-
self.include_spaces = False
|
|
184
|
-
self.MAX_LENGTH = 30
|
|
185
|
-
elif which_grammar == 1:
|
|
186
|
-
self.grammar_str_good = """
|
|
187
|
-
<sentence> ::= <subject> <verb> <object>
|
|
188
|
-
<subject> ::= The cat | A dog | The bird | A child | <adjective> <animal>
|
|
189
|
-
<animal> ::= cat | dog | bird | child
|
|
190
|
-
<adjective> ::= happy | sad | angry | playful
|
|
191
|
-
<verb> ::= chases | sees | hates | loves
|
|
192
|
-
<object> ::= the ball | the toy | the tree | <adjective> <object>
|
|
193
|
-
"""
|
|
194
|
-
self.grammar_str_bad = """
|
|
195
|
-
<sentence> ::= <subject> <verb> <object>
|
|
196
|
-
<subject> ::= The human | The dog | A bird | Some child | A <adjective> <animal>
|
|
197
|
-
<animal> ::= cat | dog | bird | child
|
|
198
|
-
<adjective> ::= happy | sad | angry | playful
|
|
199
|
-
<verb> ::= chases | sees | hates | loves
|
|
200
|
-
<object> ::= the ball | the toy | the tree | <adjective> <object>
|
|
201
|
-
"""
|
|
202
|
-
self.include_spaces = True
|
|
203
|
-
self.MAX_LENGTH = 100
|
|
204
|
-
elif which_grammar == 2:
|
|
205
|
-
self.grammar_str_good = """
|
|
206
|
-
<poem> ::= <line> | <line> <poem>
|
|
207
|
-
<line> ::= <subject> <verb> <object> <modifier>
|
|
208
|
-
<subject> ::= whispers | shadows | dreams | echoes | <compound-subject>
|
|
209
|
-
<compound-subject> ::= <subject> and <subject>
|
|
210
|
-
<verb> ::= dance | dissolve | shimmer | collapse | <compound-verb>
|
|
211
|
-
<compound-verb> ::= <verb> then <verb>
|
|
212
|
-
<object> ::= beneath | between | inside | around | <compound-object>
|
|
213
|
-
<compound-object> ::= <object> through <object>
|
|
214
|
-
<modifier> ::= silently | violently | mysteriously | endlessly | <recursive-modifier>
|
|
215
|
-
<recursive-modifier> ::= <modifier> and <modifier>
|
|
216
|
-
"""
|
|
217
|
-
self.grammar_str_bad = """
|
|
218
|
-
<bad-poem> ::= <almost-valid-line> | <bad-poem> <bad-poem>
|
|
219
|
-
<almost-valid-line> ::= <tricky-subject> <tricky-verb> <tricky-object> <tricky-modifier>
|
|
220
|
-
<tricky-subject> ::= whispers | shadows and and | <duplicate-subject>
|
|
221
|
-
<duplicate-subject> ::= whispers whispers
|
|
222
|
-
<tricky-verb> ::= dance | <incorrect-verb-placement> | <verb-verb>
|
|
223
|
-
<incorrect-verb-placement> ::= dance dance
|
|
224
|
-
<verb-verb> ::= dance whispers
|
|
225
|
-
<tricky-object> ::= beneath | <object-verb-swap> | <duplicate-object>
|
|
226
|
-
<object-verb-swap> ::= dance beneath
|
|
227
|
-
<duplicate-object> ::= beneath beneath
|
|
228
|
-
<tricky-modifier> ::= silently | <modifier-subject-swap> | <duplicate-modifier>
|
|
229
|
-
<modifier-subject-swap> ::= whispers silently
|
|
230
|
-
<duplicate-modifier> ::= silently silently
|
|
231
|
-
"""
|
|
232
|
-
self.include_spaces = True
|
|
233
|
-
self.MAX_LENGTH = 100
|
|
234
|
-
elif which_grammar == 3:
|
|
235
|
-
self.grammar_str_good = """
|
|
236
|
-
<A> ::= a <B> a |
|
|
237
|
-
<B> ::= b <C> b |
|
|
238
|
-
<C> ::= c <A> c |
|
|
239
|
-
"""
|
|
240
|
-
self.grammar_str_bad = """
|
|
241
|
-
<A> ::= a <B> c
|
|
242
|
-
<B> ::= b <C> a |
|
|
243
|
-
<C> ::= c <A> b |
|
|
244
|
-
"""
|
|
245
|
-
self.include_spaces = False
|
|
246
|
-
self.MAX_LENGTH = 100
|
|
247
|
-
|
|
248
|
-
self.grammar_good = BNF.parse_bnf(self.grammar_str_good, self.rng)
|
|
249
|
-
self.grammar_bad = BNF.parse_bnf(self.grammar_str_bad, self.rng)
|
|
250
|
-
|
|
251
|
-
self.num_answer_options = kwargs.get("num_answer_options", 4)
|
|
252
|
-
self.num_answer_blanks = kwargs.get("num_answer_blanks", 4)
|
|
253
|
-
|
|
254
|
-
def refresh(self, *args, **kwargs):
|
|
255
|
-
super().refresh(*args, **kwargs)
|
|
256
|
-
|
|
257
|
-
self.answers = {}
|
|
258
|
-
|
|
165
|
+
self.grammar_good = BNF.parse_bnf(self.grammar_str_good, self.rng)
|
|
166
|
+
self.grammar_bad = BNF.parse_bnf(self.grammar_str_bad, self.rng)
|
|
167
|
+
|
|
259
168
|
self.num_answer_options = kwargs.get("num_answer_options", 4)
|
|
260
169
|
self.num_answer_blanks = kwargs.get("num_answer_blanks", 4)
|
|
261
|
-
|
|
262
|
-
|
|
170
|
+
|
|
171
|
+
def _select_random_grammar(self):
|
|
172
|
+
"""Select and set a random grammar. Called from refresh() to ensure each PDF gets different grammar."""
|
|
173
|
+
which_grammar = self.rng.choice(range(4))
|
|
174
|
+
|
|
175
|
+
if which_grammar == 0:
|
|
176
|
+
# todo: make a few different kinds of grammars that could be picked
|
|
177
|
+
self.grammar_str_good = """
|
|
178
|
+
<expression> ::= <term> | <expression> + <term> | <expression> - <term>
|
|
179
|
+
<term> ::= <factor> | <term> * <factor> | <term> / <factor>
|
|
180
|
+
<factor> ::= <number>
|
|
181
|
+
<number> ::= <digit> | <number> <digit>
|
|
182
|
+
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
183
|
+
"""
|
|
184
|
+
# Adding in a plus to number
|
|
185
|
+
self.grammar_str_bad = """
|
|
186
|
+
<expression> ::= <term> | <expression> + <term> | <expression> - <term>
|
|
187
|
+
<term> ::= <factor> | <term> * <factor> | <term> / <factor>
|
|
188
|
+
<factor> ::= <number>
|
|
189
|
+
<number> ::= <digit> + | <digit> <number>
|
|
190
|
+
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
191
|
+
"""
|
|
192
|
+
self.include_spaces = False
|
|
193
|
+
self.MAX_LENGTH = 30
|
|
194
|
+
elif which_grammar == 1:
|
|
195
|
+
self.grammar_str_good = """
|
|
196
|
+
<sentence> ::= <subject> <verb> <object>
|
|
197
|
+
<subject> ::= The cat | A dog | The bird | A child | <adjective> <animal>
|
|
198
|
+
<animal> ::= cat | dog | bird | child
|
|
199
|
+
<adjective> ::= happy | sad | angry | playful
|
|
200
|
+
<verb> ::= chases | sees | hates | loves
|
|
201
|
+
<object> ::= the ball | the toy | the tree | <adjective> <object>
|
|
202
|
+
"""
|
|
203
|
+
self.grammar_str_bad = """
|
|
204
|
+
<sentence> ::= <subject> <verb> <object>
|
|
205
|
+
<subject> ::= The human | The dog | A bird | Some child | A <adjective> <animal>
|
|
206
|
+
<animal> ::= cat | dog | bird | child
|
|
207
|
+
<adjective> ::= happy | sad | angry | playful
|
|
208
|
+
<verb> ::= chases | sees | hates | loves
|
|
209
|
+
<object> ::= the ball | the toy | the tree | <adjective> <object>
|
|
210
|
+
"""
|
|
211
|
+
self.include_spaces = True
|
|
212
|
+
self.MAX_LENGTH = 100
|
|
213
|
+
elif which_grammar == 2:
|
|
214
|
+
self.grammar_str_good = """
|
|
215
|
+
<poem> ::= <line> | <line> <poem>
|
|
216
|
+
<line> ::= <subject> <verb> <object> <modifier>
|
|
217
|
+
<subject> ::= whispers | shadows | dreams | echoes | <compound-subject>
|
|
218
|
+
<compound-subject> ::= <subject> and <subject>
|
|
219
|
+
<verb> ::= dance | dissolve | shimmer | collapse | <compound-verb>
|
|
220
|
+
<compound-verb> ::= <verb> then <verb>
|
|
221
|
+
<object> ::= beneath | between | inside | around | <compound-object>
|
|
222
|
+
<compound-object> ::= <object> through <object>
|
|
223
|
+
<modifier> ::= silently | violently | mysteriously | endlessly | <recursive-modifier>
|
|
224
|
+
<recursive-modifier> ::= <modifier> and <modifier>
|
|
225
|
+
"""
|
|
226
|
+
self.grammar_str_bad = """
|
|
227
|
+
<bad-poem> ::= <almost-valid-line> | <bad-poem> <bad-poem>
|
|
228
|
+
<almost-valid-line> ::= <tricky-subject> <tricky-verb> <tricky-object> <tricky-modifier>
|
|
229
|
+
<tricky-subject> ::= whispers | shadows and and | <duplicate-subject>
|
|
230
|
+
<duplicate-subject> ::= whispers whispers
|
|
231
|
+
<tricky-verb> ::= dance | <incorrect-verb-placement> | <verb-verb>
|
|
232
|
+
<incorrect-verb-placement> ::= dance dance
|
|
233
|
+
<verb-verb> ::= dance whispers
|
|
234
|
+
<tricky-object> ::= beneath | <object-verb-swap> | <duplicate-object>
|
|
235
|
+
<object-verb-swap> ::= dance beneath
|
|
236
|
+
<duplicate-object> ::= beneath beneath
|
|
237
|
+
<tricky-modifier> ::= silently | <modifier-subject-swap> | <duplicate-modifier>
|
|
238
|
+
<modifier-subject-swap> ::= whispers silently
|
|
239
|
+
<duplicate-modifier> ::= silently silently
|
|
240
|
+
"""
|
|
241
|
+
self.include_spaces = True
|
|
242
|
+
self.MAX_LENGTH = 100
|
|
243
|
+
elif which_grammar == 3:
|
|
244
|
+
self.grammar_str_good = """
|
|
245
|
+
<A> ::= a <B> a |
|
|
246
|
+
<B> ::= b <C> b |
|
|
247
|
+
<C> ::= c <A> c |
|
|
248
|
+
"""
|
|
249
|
+
self.grammar_str_bad = """
|
|
250
|
+
<A> ::= a <B> c
|
|
251
|
+
<B> ::= b <C> a |
|
|
252
|
+
<C> ::= c <A> b |
|
|
253
|
+
"""
|
|
254
|
+
self.include_spaces = False
|
|
255
|
+
self.MAX_LENGTH = 100
|
|
256
|
+
|
|
257
|
+
self.grammar_good = BNF.parse_bnf(self.grammar_str_good, self.rng)
|
|
258
|
+
self.grammar_bad = BNF.parse_bnf(self.grammar_str_bad, self.rng)
|
|
263
259
|
|
|
264
260
|
def refresh(self, *args, **kwargs):
|
|
265
261
|
super().refresh(*args, **kwargs)
|
|
266
|
-
|
|
262
|
+
|
|
263
|
+
# Re-select random grammar for each refresh if not using fixed grammars
|
|
264
|
+
if not self.fixed_grammars:
|
|
265
|
+
self._select_random_grammar()
|
|
266
|
+
|
|
267
267
|
self.answers = {}
|
|
268
268
|
|
|
269
269
|
self.answers.update(
|
|
@@ -337,46 +337,55 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
337
337
|
)
|
|
338
338
|
|
|
339
339
|
|
|
340
|
-
def
|
|
340
|
+
def _get_body(self, *args, **kwargs):
|
|
341
|
+
"""Build question body and collect answers."""
|
|
342
|
+
answers = list(self.answers.values())
|
|
343
|
+
|
|
341
344
|
body = ContentAST.Section()
|
|
342
|
-
|
|
343
|
-
body.
|
|
344
|
-
ContentAST.
|
|
345
|
-
ContentAST.
|
|
346
|
-
|
|
347
|
-
]),
|
|
348
|
-
ContentAST.OnlyLatex([
|
|
349
|
-
ContentAST.Text(
|
|
350
|
-
"Given the following grammar "
|
|
351
|
-
"please circle any provided strings that are part of the language (or indicate clearly if there are none), "
|
|
352
|
-
"and on each blank line provide generate a new, unique string that is part of the language."
|
|
353
|
-
)
|
|
345
|
+
|
|
346
|
+
body.add_element(
|
|
347
|
+
ContentAST.OnlyHtml([
|
|
348
|
+
ContentAST.Paragraph([
|
|
349
|
+
"Given the following grammar, which of the below strings are part of the language?"
|
|
354
350
|
])
|
|
355
351
|
])
|
|
356
|
-
])
|
|
357
|
-
|
|
358
|
-
body.add_element(
|
|
359
|
-
ContentAST.Code(self.grammar_good.get_grammar_string())
|
|
360
352
|
)
|
|
361
|
-
|
|
362
|
-
# Add in some answers as latex-only options to be circled
|
|
363
353
|
body.add_element(
|
|
364
354
|
ContentAST.OnlyLatex([
|
|
365
|
-
ContentAST.
|
|
366
|
-
|
|
355
|
+
ContentAST.Paragraph([
|
|
356
|
+
"Given the following grammar "
|
|
357
|
+
"please circle any provided strings that are part of the language (or indicate clearly if there are none), "
|
|
358
|
+
"and on each blank line provide generate a new, unique string that is part of the language."
|
|
359
|
+
])
|
|
367
360
|
])
|
|
368
361
|
)
|
|
369
|
-
|
|
362
|
+
|
|
363
|
+
body.add_element(
|
|
364
|
+
ContentAST.Code(self.grammar_good.get_grammar_string())
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Add in some answers as latex-only options to be circled
|
|
368
|
+
latex_list = ContentAST.OnlyLatex([])
|
|
369
|
+
for answer in self.featured_answers:
|
|
370
|
+
latex_list.add_element(ContentAST.Paragraph([f"- `{str(answer)}`"]))
|
|
371
|
+
body.add_element(latex_list)
|
|
372
|
+
|
|
370
373
|
# For Latex-only, ask students to generate some more.
|
|
371
374
|
body.add_element(
|
|
372
375
|
ContentAST.OnlyLatex([
|
|
373
|
-
ContentAST.AnswerBlock([
|
|
376
|
+
ContentAST.AnswerBlock([Answer.string(f"blank_line_{i}", "", label="") for i in range(self.num_answer_blanks)])
|
|
374
377
|
])
|
|
375
378
|
)
|
|
376
|
-
|
|
379
|
+
|
|
380
|
+
return body, answers
|
|
381
|
+
|
|
382
|
+
def get_body(self, *args, **kwargs) -> ContentAST.Section:
|
|
383
|
+
"""Build question body (backward compatible interface)."""
|
|
384
|
+
body, _ = self._get_body(*args, **kwargs)
|
|
377
385
|
return body
|
|
378
386
|
|
|
379
|
-
def
|
|
387
|
+
def _get_explanation(self, *args, **kwargs):
|
|
388
|
+
"""Build question explanation."""
|
|
380
389
|
explanation = ContentAST.Section()
|
|
381
390
|
explanation.add_element(
|
|
382
391
|
ContentAST.Paragraph([
|
|
@@ -384,6 +393,11 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
384
393
|
"Unfortunately, there isn't space here to demonstrate the derivation so please work through them on your own!"
|
|
385
394
|
])
|
|
386
395
|
)
|
|
396
|
+
return explanation, []
|
|
397
|
+
|
|
398
|
+
def get_explanation(self, *args, **kwargs) -> ContentAST.Section:
|
|
399
|
+
"""Build question explanation (backward compatible interface)."""
|
|
400
|
+
explanation, _ = self._get_explanation(*args, **kwargs)
|
|
387
401
|
return explanation
|
|
388
402
|
|
|
389
403
|
def get_answers(self, *args, **kwargs) -> Tuple[Answer.AnswerKind, List[Dict[str,Any]]]:
|
|
@@ -31,11 +31,16 @@ class BitsAndBytes(MathQuestion):
|
|
|
31
31
|
self.num_bytes = int(math.pow(2, self.num_bits))
|
|
32
32
|
|
|
33
33
|
if self.from_binary:
|
|
34
|
-
self.answers = {"answer"
|
|
34
|
+
self.answers = {"answer": Answer.integer("num_bytes", self.num_bytes,
|
|
35
|
+
label="Address space size", unit="Bytes")}
|
|
35
36
|
else:
|
|
36
|
-
self.answers = {"answer"
|
|
37
|
+
self.answers = {"answer": Answer.integer("num_bits", self.num_bits,
|
|
38
|
+
label="Number of bits in address", unit="bits")}
|
|
37
39
|
|
|
38
|
-
def
|
|
40
|
+
def _get_body(self, **kwargs):
|
|
41
|
+
"""Build question body and collect answers."""
|
|
42
|
+
answers = [self.answers['answer']]
|
|
43
|
+
|
|
39
44
|
body = ContentAST.Section()
|
|
40
45
|
body.add_element(
|
|
41
46
|
ContentAST.Paragraph([
|
|
@@ -45,31 +50,17 @@ class BitsAndBytes(MathQuestion):
|
|
|
45
50
|
f"{'do we need to address our memory' if not self.from_binary else 'of memory can be addressed'}?"
|
|
46
51
|
])
|
|
47
52
|
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
),
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
else:
|
|
60
|
-
body.add_element(
|
|
61
|
-
ContentAST.AnswerBlock(
|
|
62
|
-
ContentAST.Answer(
|
|
63
|
-
answer=self.answers['answer'],
|
|
64
|
-
label="Number of bits in address",
|
|
65
|
-
unit="bits"
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
|
|
53
|
+
|
|
54
|
+
body.add_element(ContentAST.AnswerBlock(self.answers['answer']))
|
|
55
|
+
|
|
56
|
+
return body, answers
|
|
57
|
+
|
|
58
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
59
|
+
"""Build question body (backward compatible interface)."""
|
|
60
|
+
body, _ = self._get_body(**kwargs)
|
|
70
61
|
return body
|
|
71
|
-
|
|
72
|
-
def
|
|
62
|
+
|
|
63
|
+
def _get_explanation(self, **kwargs):
|
|
73
64
|
explanation = ContentAST.Section()
|
|
74
65
|
|
|
75
66
|
explanation.add_element(
|
|
@@ -94,7 +85,12 @@ class BitsAndBytes(MathQuestion):
|
|
|
94
85
|
explanation.add_element(
|
|
95
86
|
ContentAST.Equation(f"log_{{2}}({self.num_bytes} \\text{{bytes}}) = \\textbf{{{self.num_bits}}}\\text{{bits}}")
|
|
96
87
|
)
|
|
97
|
-
|
|
88
|
+
|
|
89
|
+
return explanation, []
|
|
90
|
+
|
|
91
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
92
|
+
"""Build question explanation (backward compatible interface)."""
|
|
93
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
98
94
|
return explanation
|
|
99
95
|
|
|
100
96
|
|
|
@@ -115,13 +111,18 @@ class HexAndBinary(MathQuestion):
|
|
|
115
111
|
self.binary_val = f"0b{self.value:0{4*self.number_of_hexits}b}"
|
|
116
112
|
|
|
117
113
|
if self.from_binary:
|
|
118
|
-
self.answers['answer'] = Answer.string("hex_val", self.hex_val
|
|
114
|
+
self.answers['answer'] = Answer.string("hex_val", self.hex_val,
|
|
115
|
+
label="Value in hex")
|
|
119
116
|
else:
|
|
120
|
-
self.answers['answer'] = Answer.string("binary_val", self.binary_val
|
|
117
|
+
self.answers['answer'] = Answer.string("binary_val", self.binary_val,
|
|
118
|
+
label="Value in binary")
|
|
121
119
|
|
|
122
|
-
def
|
|
120
|
+
def _get_body(self, **kwargs):
|
|
121
|
+
"""Build question body and collect answers."""
|
|
122
|
+
answers = [self.answers['answer']]
|
|
123
|
+
|
|
123
124
|
body = ContentAST.Section()
|
|
124
|
-
|
|
125
|
+
|
|
125
126
|
body.add_element(
|
|
126
127
|
ContentAST.Paragraph([
|
|
127
128
|
f"Given the number {self.hex_val if not self.from_binary else self.binary_val} "
|
|
@@ -129,19 +130,17 @@ class HexAndBinary(MathQuestion):
|
|
|
129
130
|
"Please include base indicator all padding zeros as appropriate (e.g. 0x01 should be 0b00000001)",
|
|
130
131
|
])
|
|
131
132
|
)
|
|
132
|
-
|
|
133
|
-
body.add_element(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
133
|
+
|
|
134
|
+
body.add_element(ContentAST.AnswerBlock(self.answers['answer']))
|
|
135
|
+
|
|
136
|
+
return body, answers
|
|
137
|
+
|
|
138
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
139
|
+
"""Build question body (backward compatible interface)."""
|
|
140
|
+
body, _ = self._get_body(**kwargs)
|
|
142
141
|
return body
|
|
143
|
-
|
|
144
|
-
def
|
|
142
|
+
|
|
143
|
+
def _get_explanation(self, **kwargs):
|
|
145
144
|
explanation = ContentAST.Section()
|
|
146
145
|
|
|
147
146
|
paragraph = ContentAST.Paragraph([
|
|
@@ -188,10 +187,15 @@ class HexAndBinary(MathQuestion):
|
|
|
188
187
|
f"Which gives us our binary value of: 0b{binary_str}"
|
|
189
188
|
])
|
|
190
189
|
)
|
|
191
|
-
|
|
190
|
+
|
|
191
|
+
return explanation, []
|
|
192
|
+
|
|
193
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
194
|
+
"""Build question explanation (backward compatible interface)."""
|
|
195
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
192
196
|
return explanation
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
|
|
198
|
+
|
|
195
199
|
@QuestionRegistry.register()
|
|
196
200
|
class AverageMemoryAccessTime(MathQuestion):
|
|
197
201
|
|
|
@@ -219,7 +223,8 @@ class AverageMemoryAccessTime(MathQuestion):
|
|
|
219
223
|
self.amat = self.hit_rate * self.hit_latency + (1 - self.hit_rate) * self.miss_latency
|
|
220
224
|
|
|
221
225
|
self.answers = {
|
|
222
|
-
"amat": Answer.float_value("answer__amat", self.amat
|
|
226
|
+
"amat": Answer.float_value("answer__amat", self.amat,
|
|
227
|
+
label="Average Memory Access Time", unit="cycles")
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
# Finally, do the self.rngizing of the question, to avoid these being non-deterministic
|
|
@@ -228,9 +233,12 @@ class AverageMemoryAccessTime(MathQuestion):
|
|
|
228
233
|
# At this point, everything in the question should be set.
|
|
229
234
|
pass
|
|
230
235
|
|
|
231
|
-
def
|
|
236
|
+
def _get_body(self, **kwargs):
|
|
237
|
+
"""Build question body and collect answers."""
|
|
238
|
+
answers = [self.answers["amat"]]
|
|
239
|
+
|
|
232
240
|
body = ContentAST.Section()
|
|
233
|
-
|
|
241
|
+
|
|
234
242
|
# Add in background information
|
|
235
243
|
body.add_element(
|
|
236
244
|
ContentAST.Paragraph([
|
|
@@ -245,32 +253,31 @@ class AverageMemoryAccessTime(MathQuestion):
|
|
|
245
253
|
["Hit Latency", f"{self.hit_latency} cycles"],
|
|
246
254
|
["Miss Latency", f"{self.miss_latency} cycles"]
|
|
247
255
|
]
|
|
248
|
-
|
|
256
|
+
|
|
249
257
|
# Add in either miss rate or hit rate -- we only need one of them
|
|
250
258
|
if self.show_miss_rate:
|
|
251
259
|
table_data.append(["Miss Rate", f"{100 * (1 - self.hit_rate): 0.2f}%"])
|
|
252
260
|
else:
|
|
253
261
|
table_data.append(["Hit Rate", f"{100 * self.hit_rate: 0.2f}%"])
|
|
254
|
-
|
|
262
|
+
|
|
255
263
|
body.add_element(
|
|
256
264
|
ContentAST.Table(
|
|
257
265
|
data=table_data
|
|
258
266
|
)
|
|
259
267
|
)
|
|
260
|
-
|
|
261
|
-
body.add_element(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
|
|
268
|
+
|
|
269
|
+
body.add_element(ContentAST.LineBreak())
|
|
270
|
+
|
|
271
|
+
body.add_element(ContentAST.AnswerBlock(self.answers["amat"]))
|
|
272
|
+
|
|
273
|
+
return body, answers
|
|
274
|
+
|
|
275
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
276
|
+
"""Build question body (backward compatible interface)."""
|
|
277
|
+
body, _ = self._get_body(**kwargs)
|
|
271
278
|
return body
|
|
272
|
-
|
|
273
|
-
def
|
|
279
|
+
|
|
280
|
+
def _get_explanation(self, **kwargs):
|
|
274
281
|
explanation = ContentAST.Section()
|
|
275
282
|
|
|
276
283
|
# Add in General explanation
|
|
@@ -292,6 +299,11 @@ class AverageMemoryAccessTime(MathQuestion):
|
|
|
292
299
|
]
|
|
293
300
|
)
|
|
294
301
|
)
|
|
295
|
-
|
|
302
|
+
|
|
303
|
+
return explanation, []
|
|
304
|
+
|
|
305
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
306
|
+
"""Build question explanation (backward compatible interface)."""
|
|
307
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
296
308
|
return explanation
|
|
297
309
|
|