Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagecell
Path: blob/master/exercise.py
447 views
1
from interact_sagecell import interact,Button,HtmlBox, UpdateButton
2
3
4
#################
5
6
# The following code is from https://github.com/sagemath/cloud/blob/master/sage_salvus.py
7
# copyright William Stein, distributed under the GPL v2+
8
# it doesn't quite work yet.
9
10
11
##########################################################
12
# A "%exercise" cell mode -- a first step toward
13
# automated homework.
14
##########################################################
15
class Exercise:
16
def __init__(self, question, answer, check=None, hints=None):
17
import sage.all, sage.structure.element
18
if not (isinstance(answer, (tuple, list)) and len(answer) == 2):
19
if isinstance(answer, sage.structure.element.Matrix):
20
default = sage.all.parent(answer)(0)
21
else:
22
default = ''
23
answer = [answer, default]
24
25
if check is None:
26
R = sage.all.parent(answer[0])
27
def check(attempt):
28
return R(attempt) == answer[0]
29
30
if hints is None:
31
hints = ['','','',"The answer is %s."%answer[0]]
32
33
self._question = question
34
self._answer = answer
35
self._check = check
36
self._hints = hints
37
38
def _check_attempt(self, attempt):
39
40
from sage.misc.all import walltime
41
response = "<div class='well'>"
42
correct=False
43
try:
44
r = self._check(attempt)
45
if isinstance(r, tuple) and len(r)==2:
46
correct = r[0]
47
comment = r[1]
48
else:
49
correct = bool(r)
50
comment = ''
51
except TypeError as msg:
52
response += "<h3 style='color:darkgreen'>Huh? -- %s (attempt=%s)</h3>"%(msg, attempt)
53
else:
54
if correct:
55
response += "<h1 style='color:blue'>RIGHT!</h1>"
56
if self._start_time:
57
response += "<h2 class='lighten'>Time: %.1f seconds</h2>"%(walltime()-self._start_time,)
58
if self._number_of_attempts == 1:
59
response += "<h3 class='lighten'>You got it first try!</h3>"
60
else:
61
response += "<h3 class='lighten'>It took you %s attempts.</h3>"%(self._number_of_attempts,)
62
else:
63
response += "<h3 style='color:darkgreen'>Not correct yet...</h3>"
64
if self._number_of_attempts == 1:
65
response += "<h4 style='lighten'>(first attempt)</h4>"
66
else:
67
response += "<h4 style='lighten'>(%s attempts)</h4>"%self._number_of_attempts
68
69
if self._number_of_attempts > len(self._hints):
70
hint = self._hints[-1]
71
else:
72
hint = self._hints[self._number_of_attempts-1]
73
if hint:
74
response += "<span class='lighten'>(HINT: %s)</span>"%(hint,)
75
if comment:
76
response += '<h4>%s</h4>'%comment
77
78
response += "</div>"
79
80
#interact.feedback = response#HtmlBox(response,label='')
81
82
return correct, response
83
84
def ask(self, cb):
85
from sage.misc.all import walltime
86
self._start_time = walltime()
87
self._number_of_attempts = 0
88
attempts = []
89
@interact(layout=[[('question',12)],[('attempt',12)], [('submit',12)],[('feedback',12)]])
90
def f(fself, question = ("Question:", HtmlBox(self._question)),
91
attempt = ('Answer:',self._answer[1]),
92
submit = UpdateButton('Submit'),
93
feedback = HtmlBox('')):
94
if 'attempt' in fself._changed and attempt != '':
95
attempts.append(attempt)
96
if self._start_time == 0:
97
self._start_time = walltime()
98
self._number_of_attempts += 1
99
correct, fself.feedback = self._check_attempt(attempt)
100
if correct:
101
cb({'attempts':attempts, 'time':walltime()-self._start_time})
102
103
def exercise(code):
104
r"""
105
Use the %exercise cell decorator to create interactive exercise
106
sets. Put %exercise at the top of the cell, then write Sage code
107
in the cell that defines the following (all are optional):
108
109
- a ``question`` variable, as an HTML string with math in dollar
110
signs
111
112
- an ``answer`` variable, which can be any object, or a pair
113
(correct_value, interact control) -- see the docstring for
114
interact for controls.
115
116
- an optional callable ``check(answer)`` that returns a boolean or
117
a 2-tuple
118
119
(True or False, message),
120
121
where the first argument is True if the answer is correct, and
122
the optional second argument is a message that should be
123
displayed in response to the given answer. NOTE: Often the
124
input "answer" will be a string, so you may have to use Integer,
125
RealNumber, or sage_eval to evaluate it, depending
126
on what you want to allow the user to do.
127
128
- hints -- optional list of strings to display in sequence each
129
time the user enters a wrong answer. The last string is
130
displayed repeatedly. If hints is omitted, the correct answer
131
is displayed after three attempts.
132
133
NOTE: The code that defines the exercise is executed so that it
134
does not impact (and is not impacted by) the global scope of your
135
variables elsewhere in your session. Thus you can have many
136
%exercise cells in a single worksheet with no interference between
137
them.
138
139
The following examples further illustrate how %exercise works.
140
141
An exercise to test your ability to sum the first $n$ integers::
142
143
%exercise
144
title = "Sum the first n integers, like Gauss did."
145
n = randint(3, 100)
146
question = "What is the sum $1 + 2 + \\cdots + %s$ of the first %s positive integers?"%(n,n)
147
answer = n*(n+1)//2
148
149
Transpose a matrix::
150
151
%exercise
152
title = r"Transpose a $2 \times 2$ Matrix"
153
A = random_matrix(ZZ,2)
154
question = "What is the transpose of $%s?$"%latex(A)
155
answer = A.transpose()
156
157
Add together a few numbers::
158
159
%exercise
160
k = randint(2,5)
161
title = "Add %s numbers"%k
162
v = [randint(1,10) for _ in range(k)]
163
question = "What is the sum $%s$?"%(' + '.join([str(x) for x in v]))
164
answer = sum(v)
165
166
The trace of a matrix::
167
168
%exercise
169
title = "Compute the trace of a matrix."
170
A = random_matrix(ZZ, 3, x=-5, y = 5)^2
171
question = "What is the trace of $$%s?$$"%latex(A)
172
answer = A.trace()
173
174
Some basic arithmetic with hints and dynamic feedback::
175
176
%exercise
177
k = randint(2,5)
178
title = "Add %s numbers"%k
179
v = [randint(1,10) for _ in range(k)]
180
question = "What is the sum $%s$?"%(' + '.join([str(x) for x in v]))
181
answer = sum(v)
182
hints = ['This is basic arithmetic.', 'The sum is near %s.'%(answer+randint(1,5)), "The answer is %s."%answer]
183
def check(attempt):
184
c = Integer(attempt) - answer
185
if c == 0:
186
return True
187
if abs(c) >= 10:
188
return False, "Gees -- not even close!"
189
if c < 0:
190
return False, "too low"
191
if c > 0:
192
return False, "too high"
193
194
195
Another example::
196
197
%exercise
198
title = r""
199
rank = randint(2,4)
200
A = random_matrix(QQ,5,algorithm='echelonizable', rank=rank,upper_bound=10)
201
kernel = A.T.kernel()
202
question = "Find a basis for the nullspace of $%s$. Your answer should be a list of vectors (e.g., '[(1,2,3), (3,2,1)]' )"%latex(A)
203
def check(a):
204
try:
205
a = sage_eval(a)
206
i = [vector(QQ,j) for j in a]
207
except:
208
return False, "There was an error parsing your answer. Your answer should be a list of vectors (e.g., '[(1,2,3), (3,2,1)]' )."
209
v = span(i)
210
if v.dimension()!=len(i):
211
return False, "Are your vectors linearly independent?"
212
elif v != kernel:
213
return False, "You are missing some vectors"
214
else:
215
return True, "Great job!"
216
hints = ["The RREF is $%s$."%latex(A.rref())]
217
hints.append(" ".join(hints)+" The nullity is %d."%kernel.dimension())
218
219
"""
220
f = closure(code)
221
def g():
222
x = f()
223
return x.get('title',''), x.get('question', ''), x.get('answer',''), x.get('check',None), x.get('hints',None)
224
225
title, question, answer, check, hints = g()
226
obj = {}
227
obj['E'] = Exercise(question, answer, check, hints)
228
obj['title'] = title
229
title_html = '<h3>%s</h3>'
230
231
the_times = []
232
@interact(layout=[[('go',1), ('title',11,'')],[('_output')], [('times',12, "<b>Times:</b>")]])#, flicker=True)
233
def h(self, go = Button(text="New Question", label=''),
234
title = HtmlBox(title_html%title),
235
times = HtmlBox('No times')):
236
c = self._changed
237
if 'go' in c or 'another' in c:
238
self.title = title
239
def cb(obj):
240
the_times.append("%.1f"%obj['time'])
241
h.times = ', '.join(the_times)
242
243
obj['E'].ask(cb)
244
title, question, answer, check, hints = g() # get ready for next time.
245
#print title, question, answer, check, hints
246
obj['title'] = title_html%title
247
obj['E'] = Exercise(question, answer, check, hints)
248
249
def closure(code):
250
"""
251
Wrap the given code block (a string) in a closure, i.e., a
252
function with an obfuscated random name.
253
254
When called, the function returns locals().
255
"""
256
import uuid
257
# TODO: strip string literals first
258
code = ' ' + ('\n '.join(code.splitlines()))
259
fname = "__" + str(uuid.uuid4()).replace('-','_')
260
closure = "def %s():\n%s\n return locals()"%(fname, code)
261
class Closure:
262
def __call__(self):
263
return self._f()
264
c = Closure()
265
get_ipython().run_cell(closure)
266
import sys
267
c._f = sys._sage_.namespace[fname]
268
del sys._sage_.namespace[fname]
269
return c
270
#######################
271
## end salvus code
272
#######################
273
274
imports = {'exercise': exercise}
275
276