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
|
@@ -54,7 +54,15 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
54
54
|
),
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
def
|
|
57
|
+
def _get_body(self, *args, **kwargs):
|
|
58
|
+
"""Build question body and collect answers."""
|
|
59
|
+
answers = [
|
|
60
|
+
self.answers["answer__rotational_delay"],
|
|
61
|
+
self.answers["answer__access_delay"],
|
|
62
|
+
self.answers["answer__transfer_delay"],
|
|
63
|
+
self.answers["answer__disk_access_delay"],
|
|
64
|
+
]
|
|
65
|
+
|
|
58
66
|
# Create parameter info table using mixin
|
|
59
67
|
parameter_info = {
|
|
60
68
|
"Hard Drive Rotation Speed": f"{self.hard_drive_rotation_speed}RPM",
|
|
@@ -84,8 +92,8 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
84
92
|
intro_text = "Given the information below, please calculate the following values."
|
|
85
93
|
|
|
86
94
|
instructions = (
|
|
87
|
-
f"Make sure your answers
|
|
88
|
-
f"
|
|
95
|
+
f"Make sure that if you round your answers you use the unrounded values for your final calculations, "
|
|
96
|
+
f"otherwise you may introduce error into your calculations."
|
|
89
97
|
f"(i.e. don't use your rounded answers to calculate your overall answer)"
|
|
90
98
|
)
|
|
91
99
|
|
|
@@ -96,9 +104,14 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
96
104
|
additional_instructions=instructions
|
|
97
105
|
)
|
|
98
106
|
|
|
107
|
+
return body, answers
|
|
108
|
+
|
|
109
|
+
def get_body(self, *args, **kwargs) -> ContentAST.Section:
|
|
110
|
+
"""Build question body (backward compatible interface)."""
|
|
111
|
+
body, _ = self._get_body(*args, **kwargs)
|
|
99
112
|
return body
|
|
100
|
-
|
|
101
|
-
def
|
|
113
|
+
|
|
114
|
+
def _get_explanation(self):
|
|
102
115
|
explanation = ContentAST.Section()
|
|
103
116
|
|
|
104
117
|
explanation.add_element(
|
|
@@ -150,6 +163,11 @@ class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
150
163
|
f"= {self.number_of_reads} \\cdot {self.access_delay:0.2f} + {self.transfer_delay:0.2f} "
|
|
151
164
|
f"= {self.disk_access_delay:0.2f}ms")
|
|
152
165
|
])
|
|
166
|
+
return explanation, []
|
|
167
|
+
|
|
168
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
169
|
+
"""Build question explanation (backward compatible interface)."""
|
|
170
|
+
explanation, _ = self._get_explanation()
|
|
153
171
|
return explanation
|
|
154
172
|
|
|
155
173
|
|
|
@@ -178,7 +196,15 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
178
196
|
"answer__inode_index_in_block": Answer.integer("answer__inode_index_in_block", self.inode_index_in_block),
|
|
179
197
|
})
|
|
180
198
|
|
|
181
|
-
def
|
|
199
|
+
def _get_body(self):
|
|
200
|
+
"""Build question body and collect answers."""
|
|
201
|
+
answers = [
|
|
202
|
+
self.answers["answer__inode_address"],
|
|
203
|
+
self.answers["answer__inode_block"],
|
|
204
|
+
self.answers["answer__inode_address_in_block"],
|
|
205
|
+
self.answers["answer__inode_index_in_block"],
|
|
206
|
+
]
|
|
207
|
+
|
|
182
208
|
# Create parameter info table using mixin
|
|
183
209
|
parameter_info = {
|
|
184
210
|
"Block Size": f"{self.block_size} Bytes",
|
|
@@ -206,21 +232,21 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
206
232
|
# Use mixin to create complete body with both tables
|
|
207
233
|
intro_text = "Given the information below, please calculate the following values."
|
|
208
234
|
|
|
209
|
-
instructions = (
|
|
210
|
-
"(hint: they should all be round numbers). "
|
|
211
|
-
"Remember, demonstrating you know the equations and what goes into them is generally sufficient."
|
|
212
|
-
)
|
|
213
|
-
|
|
214
235
|
body = self.create_parameter_calculation_body(
|
|
215
236
|
intro_text=intro_text,
|
|
216
237
|
parameter_table=parameter_table,
|
|
217
238
|
answer_table=answer_table,
|
|
218
|
-
additional_instructions=instructions
|
|
239
|
+
# additional_instructions=instructions
|
|
219
240
|
)
|
|
220
241
|
|
|
242
|
+
return body, answers
|
|
243
|
+
|
|
244
|
+
def get_body(self) -> ContentAST.Section:
|
|
245
|
+
"""Build question body (backward compatible interface)."""
|
|
246
|
+
body, _ = self._get_body()
|
|
221
247
|
return body
|
|
222
|
-
|
|
223
|
-
def
|
|
248
|
+
|
|
249
|
+
def _get_explanation(self):
|
|
224
250
|
explanation = ContentAST.Section()
|
|
225
251
|
|
|
226
252
|
explanation.add_element(
|
|
@@ -292,7 +318,12 @@ class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
292
318
|
f"{self.inode_index_in_block}"
|
|
293
319
|
]
|
|
294
320
|
))
|
|
295
|
-
|
|
321
|
+
|
|
322
|
+
return explanation, []
|
|
323
|
+
|
|
324
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
325
|
+
"""Build question explanation (backward compatible interface)."""
|
|
326
|
+
explanation, _ = self._get_explanation()
|
|
296
327
|
return explanation
|
|
297
328
|
|
|
298
329
|
|
|
@@ -330,40 +361,42 @@ class VSFS_states(IOQuestion):
|
|
|
330
361
|
f"{operations[-1]['cmd']}",
|
|
331
362
|
kind=Answer.AnswerKind.MULTIPLE_DROPDOWN,
|
|
332
363
|
correct=True,
|
|
333
|
-
baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']]))
|
|
364
|
+
baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']])),
|
|
365
|
+
label="Command"
|
|
334
366
|
)
|
|
335
367
|
|
|
336
|
-
def
|
|
368
|
+
def _get_body(self):
|
|
369
|
+
"""Build question body and collect answers."""
|
|
370
|
+
answers = [self.answers["answer__cmd"]]
|
|
371
|
+
|
|
337
372
|
body = ContentAST.Section()
|
|
338
|
-
|
|
373
|
+
|
|
339
374
|
body.add_element(ContentAST.Paragraph(["What operation happens between these two states?"]))
|
|
340
|
-
|
|
375
|
+
|
|
341
376
|
body.add_element(
|
|
342
377
|
ContentAST.Code(
|
|
343
378
|
self.start_state,
|
|
344
379
|
make_small=True
|
|
345
380
|
)
|
|
346
381
|
)
|
|
347
|
-
|
|
348
|
-
body.add_element(
|
|
349
|
-
|
|
350
|
-
ContentAST.Answer(
|
|
351
|
-
self.answers["answer__cmd"],
|
|
352
|
-
label="Command"
|
|
353
|
-
)
|
|
354
|
-
)
|
|
355
|
-
)
|
|
356
|
-
|
|
382
|
+
|
|
383
|
+
body.add_element(ContentAST.AnswerBlock(self.answers["answer__cmd"]))
|
|
384
|
+
|
|
357
385
|
body.add_element(
|
|
358
386
|
ContentAST.Code(
|
|
359
387
|
self.end_state,
|
|
360
388
|
make_small=True
|
|
361
389
|
)
|
|
362
390
|
)
|
|
363
|
-
|
|
391
|
+
|
|
392
|
+
return body, answers
|
|
393
|
+
|
|
394
|
+
def get_body(self) -> ContentAST.Section:
|
|
395
|
+
"""Build question body (backward compatible interface)."""
|
|
396
|
+
body, _ = self._get_body()
|
|
364
397
|
return body
|
|
365
|
-
|
|
366
|
-
def
|
|
398
|
+
|
|
399
|
+
def _get_explanation(self):
|
|
367
400
|
explanation = ContentAST.Section()
|
|
368
401
|
|
|
369
402
|
log.debug(f"self.start_state: {self.start_state}")
|
|
@@ -446,6 +479,11 @@ class VSFS_states(IOQuestion):
|
|
|
446
479
|
highlight_changes(self.start_state, self.end_state)
|
|
447
480
|
)
|
|
448
481
|
)
|
|
449
|
-
|
|
482
|
+
|
|
483
|
+
return explanation, []
|
|
484
|
+
|
|
485
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
486
|
+
"""Build question explanation (backward compatible interface)."""
|
|
487
|
+
explanation, _ = self._get_explanation()
|
|
450
488
|
return explanation
|
|
451
489
|
|
|
@@ -368,18 +368,28 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
368
368
|
self.answers.update({
|
|
369
369
|
"answer__average_response_time": Answer.auto_float(
|
|
370
370
|
"answer__average_response_time",
|
|
371
|
-
sum([job.response_time for job in jobs]) / len(jobs)
|
|
371
|
+
sum([job.response_time for job in jobs]) / len(jobs),
|
|
372
|
+
label="Overall average response time"
|
|
372
373
|
),
|
|
373
374
|
"answer__average_turnaround_time": Answer.auto_float(
|
|
374
375
|
"answer__average_turnaround_time",
|
|
375
|
-
sum([job.turnaround_time for job in jobs]) / len(jobs)
|
|
376
|
+
sum([job.turnaround_time for job in jobs]) / len(jobs),
|
|
377
|
+
label="Overall average TAT"
|
|
376
378
|
)
|
|
377
379
|
})
|
|
378
380
|
|
|
379
381
|
# Return whether this workload is interesting
|
|
380
382
|
return self.is_interesting()
|
|
381
383
|
|
|
382
|
-
def
|
|
384
|
+
def _get_body(self, *args, **kwargs):
|
|
385
|
+
"""
|
|
386
|
+
Build question body and collect answers.
|
|
387
|
+
Returns:
|
|
388
|
+
Tuple of (body_ast, answers_list)
|
|
389
|
+
"""
|
|
390
|
+
from typing import List
|
|
391
|
+
answers: List[Answer] = []
|
|
392
|
+
|
|
383
393
|
# Create table data for scheduling results
|
|
384
394
|
table_rows = []
|
|
385
395
|
for job_id in sorted(self.job_stats.keys()):
|
|
@@ -390,6 +400,9 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
390
400
|
"Response Time": f"answer__response_time_job{job_id}", # Answer key
|
|
391
401
|
"TAT": f"answer__turnaround_time_job{job_id}" # Answer key
|
|
392
402
|
})
|
|
403
|
+
# Collect answers for this job
|
|
404
|
+
answers.append(self.answers[f"answer__response_time_job{job_id}"])
|
|
405
|
+
answers.append(self.answers[f"answer__turnaround_time_job{job_id}"])
|
|
393
406
|
|
|
394
407
|
# Create table using mixin
|
|
395
408
|
scheduling_table = self.create_answer_table(
|
|
@@ -398,15 +411,18 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
398
411
|
answer_columns=["Response Time", "TAT"]
|
|
399
412
|
)
|
|
400
413
|
|
|
414
|
+
# Collect average answers
|
|
415
|
+
avg_response_answer = self.answers["answer__average_response_time"]
|
|
416
|
+
avg_tat_answer = self.answers["answer__average_turnaround_time"]
|
|
417
|
+
answers.append(avg_response_answer)
|
|
418
|
+
answers.append(avg_tat_answer)
|
|
419
|
+
|
|
401
420
|
# Create average answer block
|
|
402
|
-
average_block = ContentAST.AnswerBlock([
|
|
403
|
-
ContentAST.Answer(self.answers["answer__average_response_time"], label="Overall average response time"),
|
|
404
|
-
ContentAST.Answer(self.answers["answer__average_turnaround_time"], label="Overall average TAT")
|
|
405
|
-
])
|
|
421
|
+
average_block = ContentAST.AnswerBlock([avg_response_answer, avg_tat_answer])
|
|
406
422
|
|
|
407
423
|
# Use mixin to create complete body
|
|
408
424
|
intro_text = (
|
|
409
|
-
f"Given the below information, compute the required values if using
|
|
425
|
+
f"Given the below information, compute the required values if using **{self.scheduler_algorithm}** scheduling. "
|
|
410
426
|
f"Break any ties using the job number."
|
|
411
427
|
)
|
|
412
428
|
|
|
@@ -418,18 +434,28 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
418
434
|
|
|
419
435
|
body = self.create_fill_in_table_body(intro_text, instructions, scheduling_table)
|
|
420
436
|
body.add_element(average_block)
|
|
437
|
+
return body, answers
|
|
438
|
+
|
|
439
|
+
def get_body(self, *args, **kwargs) -> ContentAST.Section:
|
|
440
|
+
"""Build question body (backward compatible interface)."""
|
|
441
|
+
body, _ = self._get_body(*args, **kwargs)
|
|
421
442
|
return body
|
|
422
443
|
|
|
423
|
-
def
|
|
444
|
+
def _get_explanation(self, **kwargs):
|
|
445
|
+
"""
|
|
446
|
+
Build question explanation.
|
|
447
|
+
Returns:
|
|
448
|
+
Tuple of (explanation_ast, answers_list)
|
|
449
|
+
"""
|
|
424
450
|
explanation = ContentAST.Section()
|
|
425
|
-
|
|
451
|
+
|
|
426
452
|
explanation.add_element(
|
|
427
453
|
ContentAST.Paragraph([
|
|
428
454
|
f"To calculate the overall Turnaround and Response times using {self.scheduler_algorithm} "
|
|
429
455
|
f"we want to first start by calculating the respective target and response times of all of our individual jobs."
|
|
430
456
|
])
|
|
431
457
|
)
|
|
432
|
-
|
|
458
|
+
|
|
433
459
|
explanation.add_elements([
|
|
434
460
|
ContentAST.Paragraph([
|
|
435
461
|
"We do this by subtracting arrival time from either the completion time or the start time. That is:"
|
|
@@ -437,13 +463,13 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
437
463
|
ContentAST.Equation("Job_{TAT} = Job_{completion} - Job_{arrival\_time}"),
|
|
438
464
|
ContentAST.Equation("Job_{response} = Job_{start} - Job_{arrival\_time}"),
|
|
439
465
|
])
|
|
440
|
-
|
|
466
|
+
|
|
441
467
|
explanation.add_element(
|
|
442
468
|
ContentAST.Paragraph([
|
|
443
469
|
f"For each of our {len(self.job_stats.keys())} jobs, we can make these calculations.",
|
|
444
470
|
])
|
|
445
471
|
)
|
|
446
|
-
|
|
472
|
+
|
|
447
473
|
## Add in TAT
|
|
448
474
|
explanation.add_element(
|
|
449
475
|
ContentAST.Paragraph([
|
|
@@ -456,7 +482,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
456
482
|
for job_id in sorted(self.job_stats.keys())
|
|
457
483
|
])
|
|
458
484
|
)
|
|
459
|
-
|
|
485
|
+
|
|
460
486
|
summation_line = ' + '.join([
|
|
461
487
|
f"{self.job_stats[job_id]['TAT']:0.{self.ROUNDING_DIGITS}f}" for job_id in sorted(self.job_stats.keys())
|
|
462
488
|
])
|
|
@@ -467,8 +493,8 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
467
493
|
f"= {self.overall_stats['TAT']:0.{self.ROUNDING_DIGITS}f}",
|
|
468
494
|
])
|
|
469
495
|
)
|
|
470
|
-
|
|
471
|
-
|
|
496
|
+
|
|
497
|
+
|
|
472
498
|
## Add in Response
|
|
473
499
|
explanation.add_element(
|
|
474
500
|
ContentAST.Paragraph([
|
|
@@ -481,7 +507,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
481
507
|
for job_id in sorted(self.job_stats.keys())
|
|
482
508
|
])
|
|
483
509
|
)
|
|
484
|
-
|
|
510
|
+
|
|
485
511
|
summation_line = ' + '.join([
|
|
486
512
|
f"{self.job_stats[job_id]['Response']:0.{self.ROUNDING_DIGITS}f}" for job_id in sorted(self.job_stats.keys())
|
|
487
513
|
])
|
|
@@ -494,7 +520,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
494
520
|
"\n",
|
|
495
521
|
])
|
|
496
522
|
)
|
|
497
|
-
|
|
523
|
+
|
|
498
524
|
explanation.add_element(
|
|
499
525
|
ContentAST.Table(
|
|
500
526
|
headers=["Time", "Events"],
|
|
@@ -504,14 +530,19 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
504
530
|
]
|
|
505
531
|
)
|
|
506
532
|
)
|
|
507
|
-
|
|
533
|
+
|
|
508
534
|
explanation.add_element(
|
|
509
535
|
ContentAST.Picture(
|
|
510
536
|
img_data=self.make_image(),
|
|
511
537
|
caption="Process Scheduling Overview"
|
|
512
538
|
)
|
|
513
539
|
)
|
|
514
|
-
|
|
540
|
+
|
|
541
|
+
return explanation, []
|
|
542
|
+
|
|
543
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
544
|
+
"""Build question explanation (backward compatible interface)."""
|
|
545
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
515
546
|
return explanation
|
|
516
547
|
|
|
517
548
|
def is_interesting(self) -> bool:
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import logging
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Tuple
|
|
6
6
|
import sympy as sp
|
|
7
7
|
|
|
8
8
|
from QuizGenerator.contentast import ContentAST
|
|
@@ -39,6 +39,9 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
39
39
|
# Evaluate gradient at the specified point
|
|
40
40
|
subs_map = dict(zip(self.variables, evaluation_point))
|
|
41
41
|
|
|
42
|
+
# Format evaluation point for label
|
|
43
|
+
eval_point_str = ", ".join([f"x_{i} = {evaluation_point[i]}" for i in range(self.num_variables)])
|
|
44
|
+
|
|
42
45
|
# Create answer for each partial derivative
|
|
43
46
|
for i in range(self.num_variables):
|
|
44
47
|
answer_key = f"partial_derivative_{i}"
|
|
@@ -52,7 +55,9 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
52
55
|
raise ValueError("Complex number encountered - need to regenerate")
|
|
53
56
|
|
|
54
57
|
# Use auto_float for Canvas compatibility with integers and decimals
|
|
55
|
-
|
|
58
|
+
# Label includes the partial derivative notation
|
|
59
|
+
label = f"∂f/∂x_{i} at ({eval_point_str})"
|
|
60
|
+
self.answers[answer_key] = Answer.auto_float(answer_key, gradient_value, label=label)
|
|
56
61
|
|
|
57
62
|
def _create_gradient_vector_answer(self) -> None:
|
|
58
63
|
"""Create a single gradient vector answer for PDF format."""
|
|
@@ -72,8 +77,10 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
72
77
|
vector_str = format_vector(gradient_values)
|
|
73
78
|
self.answers["gradient_vector"] = Answer.string("gradient_vector", vector_str, pdf_only=True)
|
|
74
79
|
|
|
75
|
-
def
|
|
80
|
+
def _get_body(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
81
|
+
"""Build question body and collect answers."""
|
|
76
82
|
body = ContentAST.Section()
|
|
83
|
+
answers = []
|
|
77
84
|
|
|
78
85
|
# Display the function
|
|
79
86
|
body.add_element(
|
|
@@ -103,6 +110,8 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
103
110
|
|
|
104
111
|
# For Canvas: Use OnlyHtml to show individual partial derivatives
|
|
105
112
|
for i in range(self.num_variables):
|
|
113
|
+
answer = self.answers[f"partial_derivative_{i}"]
|
|
114
|
+
answers.append(answer)
|
|
106
115
|
body.add_element(
|
|
107
116
|
ContentAST.OnlyHtml([
|
|
108
117
|
ContentAST.Paragraph([
|
|
@@ -110,14 +119,20 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
110
119
|
f"\\left. {self._format_partial_derivative(i)} \\right|_{{{eval_point_str}}} = ",
|
|
111
120
|
inline=True
|
|
112
121
|
),
|
|
113
|
-
|
|
122
|
+
answer
|
|
114
123
|
])
|
|
115
124
|
])
|
|
116
125
|
)
|
|
117
126
|
|
|
127
|
+
return body, answers
|
|
128
|
+
|
|
129
|
+
def get_body(self, **kwargs) -> ContentAST.Section:
|
|
130
|
+
"""Build question body (backward compatible interface)."""
|
|
131
|
+
body, _ = self._get_body(**kwargs)
|
|
118
132
|
return body
|
|
119
133
|
|
|
120
|
-
def
|
|
134
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
135
|
+
"""Build question explanation."""
|
|
121
136
|
explanation = ContentAST.Section()
|
|
122
137
|
|
|
123
138
|
# Show the function and its gradient
|
|
@@ -165,6 +180,11 @@ class DerivativeQuestion(Question, abc.ABC):
|
|
|
165
180
|
])
|
|
166
181
|
)
|
|
167
182
|
|
|
183
|
+
return explanation, []
|
|
184
|
+
|
|
185
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
186
|
+
"""Build question explanation (backward compatible interface)."""
|
|
187
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
168
188
|
return explanation
|
|
169
189
|
|
|
170
190
|
|
|
@@ -264,7 +284,8 @@ class DerivativeChain(DerivativeQuestion):
|
|
|
264
284
|
f = sp.Function('f')
|
|
265
285
|
self.equation = sp.Eq(f(*self.variables), self.function)
|
|
266
286
|
|
|
267
|
-
def
|
|
287
|
+
def _get_explanation(self, **kwargs) -> Tuple[ContentAST.Section, List[Answer]]:
|
|
288
|
+
"""Build question explanation."""
|
|
268
289
|
explanation = ContentAST.Section()
|
|
269
290
|
|
|
270
291
|
# Show the composed function structure
|
|
@@ -366,4 +387,9 @@ class DerivativeChain(DerivativeQuestion):
|
|
|
366
387
|
])
|
|
367
388
|
)
|
|
368
389
|
|
|
390
|
+
return explanation, []
|
|
391
|
+
|
|
392
|
+
def get_explanation(self, **kwargs) -> ContentAST.Section:
|
|
393
|
+
"""Build question explanation (backward compatible interface)."""
|
|
394
|
+
explanation, _ = self._get_explanation(**kwargs)
|
|
369
395
|
return explanation
|