Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download
Views: 39598
1
##################################################################################
2
# #
3
# Extra code that the Salvus server makes available in the running Sage session. #
4
# #
5
##################################################################################
6
7
#########################################################################################
8
# Copyright (C) 2016, Sagemath Inc.
9
# #
10
# Distributed under the terms of the GNU General Public License (GPL), version 2+ #
11
# #
12
# http://www.gnu.org/licenses/ #
13
#########################################################################################
14
15
16
import copy, os, sys, types, re
17
18
import sage.all
19
20
21
def is_dataframe(obj):
22
if 'pandas' not in str(type(obj)):
23
# avoid having to import pandas unless it's really likely to be necessary.
24
return
25
# CRITICAL: do not import pandas at the top level since it can take up to 3s -- it's **HORRIBLE**.
26
try:
27
from pandas import DataFrame
28
except:
29
return False
30
return isinstance(obj, DataFrame)
31
32
# This reduces a lot of confusion for Sage worksheets -- people expect
33
# to be able to import from the current working directory.
34
sys.path.append('.')
35
36
salvus = None
37
def set_salvus(salvus_obj):
38
global salvus
39
salvus = salvus_obj
40
import sage_jupyter
41
sage_jupyter.salvus = salvus_obj
42
43
import json
44
from uuid import uuid4
45
def uuid():
46
return str(uuid4())
47
48
##########################################################################
49
# New function interact implementation
50
##########################################################################
51
import inspect
52
53
interacts = {}
54
55
def jsonable(x):
56
"""
57
Given any object x, make a JSON-able version of x, doing as best we can.
58
For some objects, sage as Sage integers, this works well. For other
59
objects which make no sense in Javascript, we get a string.
60
"""
61
import sage.all
62
try:
63
json.dumps(x)
64
return x
65
except:
66
if isinstance(x, (sage.all.Integer)):
67
return int(x)
68
else:
69
return str(x)
70
71
class InteractCell(object):
72
def __init__(self, f, layout=None, width=None, style=None,
73
update_args=None, auto_update=True,
74
flicker=False, output=True):
75
"""
76
Given a function f, create an object that describes an interact
77
for working with f interactively.
78
79
INPUT:
80
81
- `f` -- Python function
82
- ``width`` -- (default: None) overall width of the interact canvas
83
- ``style`` -- (default: None) extra CSS style to apply to canvas
84
- ``update_args`` -- (default: None) only call f if one of the args in
85
this list of strings changes.
86
- ``auto_update`` -- (default: True) call f every time an input changes
87
(or one of the arguments in update_args).
88
- ``flicker`` -- (default: False) if False, the output part of the cell
89
never shrinks; it can only grow, which aleviates flicker.
90
- ``output`` -- (default: True) if False, do not automatically
91
provide any area to display output.
92
"""
93
self._flicker = flicker
94
self._output = output
95
self._uuid = uuid()
96
# Prevent garbage collection until client specifically requests it,
97
# since we want to be able to store state.
98
interacts[self._uuid] = self
99
self._f = f
100
self._width = jsonable(width)
101
self._style = str(style)
102
103
(args, varargs, varkw, defaults) = inspect.getargspec(f)
104
if defaults is None:
105
defaults = []
106
107
n = len(args) - len(defaults)
108
self._controls = dict([(arg, interact_control(arg, defaults[i-n] if i >= n else None))
109
for i, arg in enumerate(args)])
110
111
self._last_vals = {}
112
for arg in args:
113
self._last_vals[arg] = self._controls[arg].default()
114
115
self._ordered_args = args
116
self._args = set(args)
117
118
if isinstance(layout, dict):
119
# Implement the layout = {'top':, 'bottom':, 'left':,
120
# 'right':} dictionary option that is in the Sage
121
# notebook. I personally think it is really awkward and
122
# unsuable, but there may be many interacts out there that
123
# use it.
124
# Example layout={'top': [['a', 'b'], ['x', 'y']], 'left': [['c']], 'bottom': [['d']]}
125
top = layout.get('top', [])
126
bottom = layout.get('bottom', [])
127
left = layout.get('left', [])
128
right = layout.get('right', [])
129
new_layout = []
130
for row in top:
131
new_layout.append(row)
132
if len(left) > 0 and len(right) > 0:
133
new_layout.append(left[0] + [''] + right[0])
134
del left[0]
135
del right[0]
136
elif len(left) > 0 and len(right) == 0:
137
new_layout.append(left[0] + [''])
138
del left[0]
139
elif len(left) == 0 and len(right) > 0:
140
new_layout.append([''] + right[0])
141
del right[0]
142
i = 0
143
while len(left) > 0 and len(right) > 0:
144
new_layout.append(left[0] + ['_salvus_'] + right[0])
145
del left[0]
146
del right[0]
147
while len(left) > 0:
148
new_layout.append(left[0])
149
del left[0]
150
while len(right) > 0:
151
new_layout.append(right[0])
152
del right[0]
153
for row in bottom:
154
new_layout.append(row)
155
layout = new_layout
156
157
if layout is None:
158
layout = [[(str(arg), 12, None)] for arg in self._ordered_args]
159
else:
160
try:
161
v = []
162
for row in layout:
163
new_row = []
164
for x in row:
165
if isinstance(x, str):
166
x = (x,)
167
if len(x) == 1:
168
new_row.append((str(x[0]), 12//len(row), None))
169
elif len(x) == 2:
170
new_row.append((str(x[0]), int(x[1]), None))
171
elif len(x) == 3:
172
new_row.append((str(x[0]), int(x[1]), str(x[2])))
173
v.append(new_row)
174
layout = v
175
except:
176
raise ValueError("layout must be None or a list of tuples (variable_name, width, [optional label]), where width is an integer between 1 and 12, variable_name is a string, and label is a string. The widths in each row must add up to at most 12. The empty string '' denotes the output area.")
177
178
# Append a row for any remaining controls:
179
layout_vars = set(sum([[x[0] for x in row] for row in layout],[]))
180
for v in args:
181
if v not in layout_vars:
182
layout.append([(v, 12, None)])
183
184
if self._output:
185
if '' not in layout_vars:
186
layout.append([('', 12, None)])
187
188
self._layout = layout
189
190
# TODO -- this is UGLY
191
if not auto_update:
192
c = button('Update')
193
c._opts['var'] = 'auto_update'
194
self._controls['auto_update'] = c
195
self._ordered_args.append("auto_update")
196
layout.append([('auto_update',2)])
197
update_args = ['auto_update']
198
199
self._update_args = update_args
200
201
def jsonable(self):
202
"""
203
Return a JSON-able description of this interact, which the client
204
can use for laying out controls.
205
"""
206
X = {'controls':[self._controls[arg].jsonable() for arg in self._ordered_args], 'id':self._uuid}
207
if self._width is not None:
208
X['width'] = self._width
209
if self._layout is not None:
210
X['layout'] = self._layout
211
X['style'] = self._style
212
X['flicker'] = self._flicker
213
return X
214
215
def __call__(self, vals):
216
"""
217
Call self._f with inputs specified by vals. Any input variables not
218
specified in vals will have the value they had last time.
219
"""
220
self.changed = [str(x) for x in vals.keys()]
221
for k, v in vals.iteritems():
222
x = self._controls[k](v)
223
self._last_vals[k] = x
224
225
if self._update_args is not None:
226
do_it = False
227
for v in self._update_args:
228
if v in self.changed:
229
do_it = True
230
if not do_it:
231
return
232
233
interact_exec_stack.append(self)
234
try:
235
self._f(**dict([(k,self._last_vals[k]) for k in self._args]))
236
finally:
237
interact_exec_stack.pop()
238
239
class InteractFunction(object):
240
def __init__(self, interact_cell):
241
self.__dict__['interact_cell'] = interact_cell
242
243
def __call__(self, **kwds):
244
salvus.clear()
245
for arg, value in kwds.iteritems():
246
self.__setattr__(arg, value)
247
return self.interact_cell(kwds)
248
249
def __setattr__(self, arg, value):
250
I = self.__dict__['interact_cell']
251
if arg in I._controls and not isinstance(value, control):
252
# setting value of existing control
253
v = I._controls[arg].convert_to_client(value)
254
desc = {'var':arg, 'default':v}
255
I._last_vals[arg] = value
256
else:
257
# create a new control
258
new_control = interact_control(arg, value)
259
I._controls[arg] = new_control
260
desc = new_control.jsonable()
261
# set the id of the containing interact
262
desc['id'] = I._uuid
263
salvus.javascript("worksheet.set_interact_var(obj)", obj=jsonable(desc))
264
265
def __getattr__(self, arg):
266
I = self.__dict__['interact_cell']
267
try:
268
return I._last_vals[arg]
269
except Exception as err:
270
print(err)
271
raise AttributeError("no interact control corresponding to input variable '%s'"%arg)
272
273
def __delattr__(self, arg):
274
I = self.__dict__['interact_cell']
275
try:
276
del I._controls[arg]
277
except KeyError:
278
pass
279
desc = {'id':I._uuid, 'name':arg}
280
salvus.javascript("worksheet.del_interact_var(obj)", obj=jsonable(desc))
281
282
def changed(self):
283
"""
284
Return the variables that changed since last evaluation of the interact function
285
body. [SALVUS only]
286
287
For example::
288
289
@interact
290
def f(n=True, m=False, xyz=[1,2,3]):
291
print n, m, xyz, interact.changed()
292
"""
293
return self.__dict__['interact_cell'].changed
294
295
class _interact_layout:
296
def __init__(self, *args):
297
self._args = args
298
def __call__(self, f):
299
return interact(f, *self._args)
300
301
class Interact(object):
302
"""
303
Use interact to create interactive worksheet cells with sliders,
304
text boxes, radio buttons, check boxes, color selectors, and more.
305
306
Put ``@interact`` on the line before a function definition in a
307
cell by itself, and choose appropriate defaults for the variable
308
names to determine the types of controls (see tables below). You
309
may also put ``@interact(layout=...)`` to control the layout of
310
controls. Within the function, you may explicitly set the value
311
of the control corresponding to a variable foo to bar by typing
312
interact.foo = bar.
313
314
Type "interact.controls.[tab]" to get access to all of the controls.
315
316
INPUT:
317
318
- ``f`` -- function
319
- ``width`` -- number, or string such as '80%', '300px', '20em'.
320
- ``style`` -- CSS style string, which allows you to change the border,
321
background color, etc., of the interact.
322
- ``update_args`` -- (default: None); list of strings, so that
323
only changing the corresponding controls causes the function to
324
be re-evaluated; changing other controls will not cause an update.
325
- ``auto_update`` -- (default: True); if False, a button labeled
326
'Update' will appear which you can click on to re-evalute.
327
- ``layout`` -- (default: one control per row) a list [row0,
328
row1, ...] of lists of tuples row0 = [(var_name, width,
329
label), ...], where the var_name's are strings, the widths
330
must add up to at most 12, and the label is optional. This
331
will layout all of the controls and output using Twitter
332
Bootstraps "Fluid layout", with spans corresponding
333
to the widths. Use var_name='' to specify where the output
334
goes, if you don't want it to last. You may specify entries for
335
controls that you will create later using interact.var_name = foo.
336
337
338
NOTES: The flicker and layout options above are only in SALVUS.
339
For backwards compatibility with the Sage notebook, if layout
340
is a dictionary (with keys 'top', 'bottom', 'left', 'right'),
341
then the appropriate layout will be rendered as it used to be
342
in the Sage notebook.
343
344
OUTPUT:
345
346
- creates an interactive control.
347
348
349
AUTOMATIC CONTROL RULES
350
-----------------------
351
352
There are also some defaults that allow you to make controls
353
automatically without having to explicitly specify them. E.g.,
354
you can make ``x`` a continuous slider of values between ``u`` and
355
``v`` by just writing ``x=(u,v)`` in the argument list.
356
357
- ``u`` - blank input_box
358
- ``u=elt`` - input_box with ``default=element``, unless other rule below
359
- ``u=(umin,umax)`` - continuous slider (really `100` steps)
360
- ``u=(umin,umax,du)`` - slider with step size ``du``
361
- ``u=list`` - buttons if ``len(list)`` at most `5`; otherwise, drop down
362
- ``u=generator`` - a slider (up to `10000` steps)
363
- ``u=bool`` - a checkbox
364
- ``u=Color('blue')`` - a color selector; returns ``Color`` object
365
- ``u=matrix`` - an ``input_grid`` with ``to_value`` set to
366
``matrix.parent()`` and default values given by the matrix
367
- ``u=(default, v)`` - ``v`` anything as above, with given ``default`` value
368
- ``u=(label, v)`` - ``v`` anything as above, with given ``label`` (a string)
369
370
EXAMPLES:
371
372
373
The layout option::
374
375
@interact(layout={'top': [['a', 'b']], 'left': [['c']],
376
'bottom': [['d']], 'right':[['e']]})
377
def _(a=x^2, b=(0..20), c=100, d=x+1, e=sin(2)):
378
print(a+b+c+d+e)
379
380
We illustrate some features that are only in Salvus, not in the
381
Sage cell server or Sage notebook.
382
383
You can set the value of a control called foo to 100 using
384
interact.foo=100. For example::
385
386
@interact
387
def f(n=20, twice=None):
388
interact.twice = int(n)*2
389
390
391
In this example, we create and delete multiple controls depending
392
on properties of the input::
393
394
@interact
395
def f(n=20, **kwds):
396
print(kwds)
397
n = Integer(n)
398
if n % 2 == 1:
399
del interact.half
400
else:
401
interact.half = input_box(n/2, readonly=True)
402
if n.is_prime():
403
interact.is_prime = input_box('True', readonly=True)
404
else:
405
del interact.is_prime
406
407
We illustrate not automatically updating the function until a
408
button is pressed::
409
410
@interact(auto_update=False)
411
def f(a=True, b=False):
412
print a, b
413
414
You can access the value of a control associated to a variable foo
415
that you create using interact.foo, and check whether there is a
416
control associated to a given variable name using hasattr::
417
418
@interact
419
def f():
420
if not hasattr(interact, 'foo'):
421
interact.foo = 'hello'
422
else:
423
print(interact.foo)
424
425
An indecisive interact::
426
427
@interact
428
def f(n=selector(['yes', 'no'])):
429
for i in range(5):
430
interact.n = i%2
431
sleep(.2)
432
433
We use the style option to make a holiday interact::
434
435
@interact(width=25,
436
style="background-color:lightgreen; border:5px dashed red;")
437
def f(x=button('Merry ...',width=20)):
438
pass
439
440
We make a little box that can be dragged around, resized, and is
441
updated via a computation (in this case, counting primes)::
442
443
@interact(width=30,
444
style="background-color:lightorange; position:absolute; z-index:1000; box-shadow : 8px 8px 4px #888;")
445
def f(prime=text_control(label="Counting primes: ")):
446
salvus.javascript("cell.element.closest('.salvus-cell-output-interact').draggable().resizable()")
447
p = 2
448
c = 1
449
while True:
450
interact.prime = '%s, %.2f'%(p, float(c)/p)
451
p = next_prime(p)
452
c += 1
453
sleep(.25)
454
"""
455
def __call__(self, f=None, layout=None, width=None, style=None, update_args=None, auto_update=True, flicker=False, output=True):
456
if f is None:
457
return _interact_layout(layout, width, style, update_args, auto_update, flicker)
458
else:
459
return salvus.interact(f, layout=layout, width=width, style=style,
460
update_args=update_args, auto_update=auto_update, flicker=flicker, output=output)
461
462
def __setattr__(self, arg, value):
463
I = interact_exec_stack[-1]
464
if arg in I._controls and not isinstance(value, control):
465
# setting value of existing control
466
v = I._controls[arg].convert_to_client(value)
467
desc = {'var':arg, 'default':v}
468
I._last_vals[arg] = value
469
else:
470
# create a new control
471
new_control = interact_control(arg, value)
472
I._controls[arg] = new_control
473
desc = new_control.jsonable()
474
desc['id'] = I._uuid
475
salvus.javascript("worksheet.set_interact_var(obj)", obj=desc)
476
477
def __delattr__(self, arg):
478
try:
479
del interact_exec_stack[-1]._controls[arg]
480
except KeyError:
481
pass
482
desc['id'] = I._uuid
483
salvus.javascript("worksheet.del_interact_var(obj)", obj=jsonable(arg))
484
485
def __getattr__(self, arg):
486
try:
487
return interact_exec_stack[-1]._last_vals[arg]
488
except Exception as err:
489
raise AttributeError("no interact control corresponding to input variable '%s'"%arg)
490
491
def changed(self):
492
"""
493
Return the variables that changed since last evaluation of the interact function
494
body. [SALVUS only]
495
496
For example::
497
498
@interact
499
def f(n=True, m=False, xyz=[1,2,3]):
500
print n, m, xyz, interact.changed()
501
"""
502
return interact_exec_stack[-1].changed
503
504
interact = Interact()
505
interact_exec_stack = []
506
507
class control:
508
def __init__(self, control_type, opts, repr, convert_from_client=None, convert_to_client=jsonable):
509
# The type of the control -- a string, used for CSS selectors, switches, etc.
510
self._control_type = control_type
511
# The options that define the control -- passed to client
512
self._opts = dict(opts)
513
# Used to print the control to a string.
514
self._repr = repr
515
# Callable that the control may use in converting from JSON
516
self._convert_from_client = convert_from_client
517
self._convert_to_client = convert_to_client
518
self._last_value = self._opts['default']
519
520
def convert_to_client(self, value):
521
try:
522
return self._convert_to_client(value)
523
except Exception as err:
524
sys.stderr.write("convert_to_client: %s -- %s\n"%(err, self))
525
sys.stderr.flush()
526
return jsonable(value)
527
528
def __call__(self, obj):
529
"""
530
Convert JSON-able object returned from client to describe
531
value of this control.
532
"""
533
if self._convert_from_client is not None:
534
try:
535
x = self._convert_from_client(obj)
536
except Exception as err:
537
sys.stderr.write("%s -- %s\n"%(err, self))
538
sys.stderr.flush()
539
x = self._last_value
540
else:
541
x = obj
542
self._last_value = x
543
return x
544
545
def __repr__(self):
546
return self._repr
547
548
def label(self):
549
"""Return the label of this control."""
550
return self._opts['label']
551
552
def default(self):
553
"""Return default value of this control."""
554
return self(self._opts['default'])
555
556
def type(self):
557
"""Return type that values of this control are coerced to."""
558
return self._opts['type']
559
560
def jsonable(self):
561
"""Return JSON-able object the client browser uses to render the control."""
562
X = {'control_type':self._control_type}
563
for k, v in self._opts.iteritems():
564
X[k] = jsonable(v)
565
return X
566
567
import types, inspect
568
569
def list_of_first_n(v, n):
570
"""Given an iterator v, return first n elements it produces as a list."""
571
if not hasattr(v, 'next'):
572
v = v.__iter__()
573
w = []
574
while n > 0:
575
try:
576
w.append(v.next())
577
except StopIteration:
578
return w
579
n -= 1
580
return w
581
582
def automatic_control(default):
583
from sage.all import Color
584
from sage.structure.element import is_Matrix
585
label = None
586
default_value = None
587
588
for _ in range(2):
589
if isinstance(default, tuple) and len(default) == 2 and isinstance(default[0], str):
590
label, default = default
591
if isinstance(default, tuple) and len(default) == 2 and hasattr(default[1],'__iter__'):
592
default_value, default = default
593
594
if isinstance(default, control):
595
if label:
596
default._opts['label'] = label
597
return default
598
elif isinstance(default, str):
599
return input_box(default, label=label, type=str)
600
elif isinstance(default, unicode):
601
return input_box(default, label=label, type=unicode)
602
elif isinstance(default, bool):
603
return checkbox(default, label=label)
604
elif isinstance(default, list):
605
return selector(default, default=default_value, label=label, buttons=len(default) <= 5)
606
elif isinstance(default, Color):
607
return color_selector(default=default, label=label)
608
elif isinstance(default, tuple):
609
if len(default) == 2:
610
return slider(default[0], default[1], default=default_value, label=label)
611
elif len(default) == 3:
612
return slider(default[0], default[1], default[2], default=default_value, label=label)
613
else:
614
return slider(list(default), default=default_value, label=label)
615
elif is_Matrix(default):
616
return input_grid(default.nrows(), default.ncols(), default=default.list(), to_value=default.parent(), label=label)
617
elif hasattr(default, '__iter__'):
618
return slider(list_of_first_n(default, 10000), default=default_value, label=label)
619
else:
620
return input_box(default, label=label)
621
622
def interact_control(arg, value):
623
if isinstance(value, control):
624
if value._opts['label'] is None:
625
value._opts['label'] = arg
626
c = value
627
else:
628
c = automatic_control(value)
629
if c._opts['label'] is None:
630
c._opts['label'] = arg
631
c._opts['var'] = arg
632
return c
633
634
def sage_eval(x, locals=None, **kwds):
635
if isinstance(x, str):
636
x = str(x).strip()
637
if x.isspace():
638
return None
639
from sage.all import sage_eval
640
return sage_eval(x, locals=locals, **kwds)
641
642
class ParseValue:
643
def __init__(self, type):
644
self._type = type
645
646
def _eval(self, value):
647
if isinstance(value, (str, unicode)):
648
if not value:
649
return ''
650
return sage_eval(value, locals=None if salvus is None else salvus.namespace)
651
else:
652
return value
653
654
def __call__(self, value):
655
from sage.all import Color
656
if self._type is None:
657
return self._eval(value)
658
elif self._type is str:
659
return str(value)
660
elif self._type is unicode:
661
return unicode(value)
662
elif self._type is Color:
663
try:
664
return Color(value)
665
except ValueError:
666
try:
667
return Color("#"+value)
668
except ValueError:
669
raise TypeError("invalid color '%s'"%value)
670
else:
671
return self._type(self._eval(value))
672
673
def input_box(default=None, label=None, type=None, nrows=1, width=None, readonly=False, submit_button=None):
674
"""
675
An input box interactive control for use with the :func:`interact` command.
676
677
INPUT:
678
679
- default -- default value
680
- label -- label test
681
- type -- the type that the input is coerced to (from string)
682
- nrows -- (default: 1) the number of rows of the box
683
- width -- width; how wide the box is
684
- readonly -- is it read-only?
685
- submit_button -- defaults to true if nrows > 1 and false otherwise.
686
"""
687
return control(
688
control_type = 'input-box',
689
opts = locals(),
690
repr = "Input box",
691
convert_from_client = ParseValue(type)
692
)
693
694
def checkbox(default=True, label=None, readonly=False):
695
"""
696
A checkbox interactive control for use with the :func:`interact` command.
697
"""
698
return control(
699
control_type = 'checkbox',
700
opts = locals(),
701
repr = "Checkbox"
702
)
703
704
def color_selector(default='blue', label=None, readonly=False, widget=None, hide_box=False):
705
"""
706
A color selector.
707
708
SALVUS only: the widget option is ignored -- SALVUS only provides
709
bootstrap-colorpicker.
710
711
EXAMPLES::
712
713
@interact
714
def f(c=color_selector()):
715
print(c)
716
"""
717
from sage.all import Color
718
default = Color(default).html_color()
719
return control(
720
control_type = 'color-selector',
721
opts = locals(),
722
repr = "Color selector",
723
convert_from_client = lambda x : Color(str(x)),
724
convert_to_client = lambda x : Color(x).html_color()
725
)
726
727
def text_control(default='', label=None, classes=None):
728
"""
729
A read-only control that displays arbitrary HTML amongst the other
730
interact controls. This is very powerful, since it can display
731
any HTML.
732
733
INPUT::
734
735
- ``default`` -- actual HTML to display
736
- ``label`` -- string or None
737
- ``classes`` -- space separated string of CSS classes
738
739
EXAMPLES::
740
741
We output the factorization of a number in a text_control::
742
743
@interact
744
def f(n=2013, fact=text_control("")):
745
interact.fact = factor(n)
746
747
We use a CSS class to make the text_control look like a button:
748
749
@interact
750
def f(n=text_control("foo <b>bar</b>", classes='btn')):
751
pass
752
753
We animate a picture into view:
754
755
@interact
756
def f(size=[10,15,..,30], speed=[1,2,3,4]):
757
for k in range(size):
758
interact.g = text_control("<img src='http://sagemath.org/pix/sage_logo_new.png' width=%s>"%(20*k))
759
sleep(speed/50.0)
760
"""
761
return control(
762
control_type = 'text',
763
opts = locals(),
764
repr = "Text %r"%(default)
765
)
766
767
def button(default=None, label=None, classes=None, width=None, icon=None):
768
"""
769
Create a button. [SALVUS only]
770
771
You can tell that pressing this button triggered the interact
772
evaluation because interact.changed() will include the variable
773
name tied to the button.
774
775
INPUT:
776
777
- ``default`` -- value variable is set to
778
- ``label`` -- string (default: None)
779
- ``classes`` -- string if None; if given, space separated
780
list of CSS classes. e.g., Bootstrap CSS classes such as:
781
btn-primary, btn-info, btn-success, btn-warning, btn-danger,
782
btn-link, btn-large, btn-small, btn-mini.
783
See http://twitter.github.com/bootstrap/base-css.html#buttons
784
If button_classes a single string, that class is applied to all buttons.
785
- ``width`` - an integer or string (default: None); if given,
786
all buttons are this width. If an integer, the default units
787
are 'ex'. A string that specifies any valid HTML units (e.g., '100px', '3em')
788
is also allowed [SALVUS only].
789
- ``icon`` -- None or string name of any icon listed at the font
790
awesome website (http://fortawesome.github.com/Font-Awesome/), e.g., 'fa-repeat'
791
792
EXAMPLES::
793
794
@interact
795
def f(hi=button('Hello', label='', classes="btn-primary btn-large"),
796
by=button("By")):
797
if 'hi' in interact.changed():
798
print("Hello to you, good sir.")
799
if 'by' in interact.changed():
800
print("See you.")
801
802
Some buttons with icons::
803
804
@interact
805
def f(n=button('repeat', icon='fa-repeat'),
806
m=button('see?', icon="fa-eye", classes="btn-large")):
807
print(interact.changed())
808
"""
809
return control(
810
control_type = "button",
811
opts = locals(),
812
repr = "Button",
813
convert_from_client = lambda x : default,
814
convert_to_client = lambda x : str(x)
815
)
816
817
818
class Slider:
819
def __init__(self, start, stop, step_size, max_steps):
820
if isinstance(start, (list, tuple)):
821
self.vals = start
822
else:
823
if step_size is None:
824
if stop is None:
825
step_size = start/float(max_steps)
826
else:
827
step_size = (stop-start)/float(max_steps)
828
from sage.all import srange # sage range is much better/more flexible.
829
self.vals = srange(start, stop, step_size, include_endpoint=True)
830
# Now check to see if any of thee above constructed a list of
831
# values that exceeds max_steps -- if so, linearly interpolate:
832
if len(self.vals) > max_steps:
833
n = len(self.vals)//max_steps
834
self.vals = [self.vals[n*i] for i in range(len(self.vals)//n)]
835
836
def to_client(self, val):
837
if val is None:
838
return 0
839
if isinstance(val, (list, tuple)):
840
return [self.to_client(v) for v in val]
841
else:
842
# Find index into self.vals of closest match.
843
try:
844
return self.vals.index(val) # exact match
845
except ValueError:
846
pass
847
z = [(abs(val-x),i) for i, x in enumerate(self.vals)]
848
z.sort()
849
return z[0][1]
850
851
def from_client(self, val):
852
if val is None:
853
return self.vals[0]
854
# val can be a n-tuple or an integer
855
if isinstance(val, (list, tuple)):
856
return tuple([self.vals[v] for v in val])
857
else:
858
return self.vals[int(val)]
859
860
class InputGrid:
861
def __init__(self, nrows, ncols, default, to_value):
862
self.nrows = nrows
863
self.ncols = ncols
864
self.to_value = to_value
865
self.value = copy.deepcopy(self.adapt(default))
866
867
def adapt(self, x):
868
if not isinstance(x, list):
869
return [[x for _ in range(self.ncols)] for _ in range(self.nrows)]
870
elif not all(isinstance(elt, list) for elt in x):
871
return [[x[i * self.ncols + j] for j in xrange(self.ncols)] for i in xrange(self.nrows)]
872
else:
873
return x
874
875
def from_client(self, x):
876
if len(x) == 0:
877
self.value = []
878
elif isinstance(x[0], list):
879
self.value = [[sage_eval(t) for t in z] for z in x]
880
else:
881
# x is a list of (unicode) strings -- we sage eval them all at once (instead of individually).
882
s = '[' + ','.join([str(t) for t in x]) + ']'
883
v = sage_eval(s)
884
self.value = [v[n:n+self.ncols] for n in range(0, self.nrows*self.ncols, self.ncols)]
885
886
return self.to_value(self.value) if self.to_value is not None else self.value
887
888
def to_client(self, x=None):
889
if x is None:
890
v = self.value
891
else:
892
v = self.adapt(x)
893
self.value = v # save value in our local cache
894
return [[repr(x) for x in y] for y in v]
895
896
897
def input_grid(nrows, ncols, default=0, label=None, to_value=None, width=5):
898
r"""
899
A grid of input boxes, for use with the :func:`interact` command.
900
901
INPUT:
902
903
- ``nrows`` - an integer
904
- ``ncols`` - an integer
905
- ``default`` - an object; the default put in this input box
906
- ``label`` - a string; the label rendered to the left of the box.
907
- ``to_value`` - a list; the grid output (list of rows) is
908
sent through this function. This may reformat the data or
909
coerce the type.
910
- ``width`` - an integer; size of each input box in characters
911
912
EXAMPLES:
913
914
Solving a system::
915
916
@interact
917
def _(m = input_grid(2,2, default = [[1,7],[3,4]],
918
label=r'$M\qquad =$', to_value=matrix, width=8),
919
v = input_grid(2,1, default=[1,2],
920
label=r'$v\qquad =$', to_value=matrix)):
921
try:
922
x = m.solve_right(v)
923
html('$$%s %s = %s$$'%(latex(m), latex(x), latex(v)))
924
except:
925
html('There is no solution to $$%s x=%s$$'%(latex(m), latex(v)))
926
927
Squaring an editable and randomizable matrix::
928
929
@interact
930
def f(reset = button('Randomize', classes="btn-primary", icon="fa-th"),
931
square = button("Square", icon="fa-external-link"),
932
m = input_grid(4,4,default=0, width=5, label="m =", to_value=matrix)):
933
if 'reset' in interact.changed():
934
print("randomize")
935
interact.m = [[random() for _ in range(4)] for _ in range(4)]
936
if 'square' in interact.changed():
937
salvus.tex(m^2)
938
939
"""
940
ig = InputGrid(nrows, ncols, default, to_value)
941
942
return control(
943
control_type = 'input-grid',
944
opts = {'default' : ig.to_client(),
945
'label' : label,
946
'width' : width,
947
'nrows' : nrows,
948
'ncols' : ncols},
949
repr = "Input Grid",
950
convert_from_client = ig.from_client,
951
convert_to_client = ig.to_client
952
)
953
954
def slider(start, stop=None, step=None, default=None, label=None,
955
display_value=True, max_steps=500, step_size=None, range=False,
956
width=None, animate=True):
957
"""
958
An interactive slider control for use with :func:`interact`.
959
960
There are several ways to call the slider function, but they all
961
take several named arguments:
962
963
- ``default`` - an object (default: None); default value is closest
964
value. If range=True, default can also be a 2-tuple (low, high).
965
- ``label`` -- string
966
- ``display_value`` -- bool (default: True); whether to display the
967
current value to the right of the slider.
968
- ``max_steps`` -- integer, default: 500; this is the maximum
969
number of values that the slider can take on. Do not make
970
it too large, since it could overwhelm the client. [SALVUS only]
971
- ``range`` -- bool (default: False); instead, you can select
972
a range of values (lower, higher), which are returned as a
973
2-tuple. You may also set the value of the slider or
974
specify a default value using a 2-tuple.
975
- ``width`` -- how wide the slider appears to the user [SALVUS only]
976
- ``animate`` -- True (default), False,"fast", "slow", or the
977
duration of the animation in milliseconds. [SALVUS only]
978
979
You may call the slider function as follows:
980
981
- slider([list of objects], ...) -- slider taking values the objects in the list
982
983
- slider([start,] stop[, step]) -- slider over numbers from start
984
to stop. When step is given it specifies the increment (or
985
decrement); if it is not given, then the number of steps equals
986
the width of the control in pixels. In all cases, the number of
987
values will be shrunk to be at most the pixel_width, since it is
988
not possible to select more than this many values using a slider.
989
990
EXAMPLES::
991
992
993
Use one slider to modify the animation speed of another::
994
995
@interact
996
def f(speed=(50,100,..,2000), x=slider([1..50], animate=1000)):
997
if 'speed' in interact.triggers():
998
print("change x to have speed {}".format(speed))
999
del interact.x
1000
interact.x = slider([1..50], default=interact.x, animate=speed)
1001
return
1002
"""
1003
if step_size is not None: # for compat with sage
1004
step = step_size
1005
slider = Slider(start, stop, step, max_steps)
1006
vals = [str(x) for x in slider.vals] # for display by the client
1007
if range and default is None:
1008
default = [0, len(vals)-1]
1009
return control(
1010
control_type = 'range-slider' if range else 'slider',
1011
opts = {'default' : slider.to_client(default),
1012
'label' : label,
1013
'animate' : animate,
1014
'vals' : vals,
1015
'display_value' : display_value,
1016
'width' : width},
1017
repr = "Slider",
1018
convert_from_client = slider.from_client,
1019
convert_to_client = slider.to_client
1020
)
1021
1022
def range_slider(*args, **kwds):
1023
"""
1024
range_slider is the same as :func:`slider`, except with range=True.
1025
1026
EXAMPLES:
1027
1028
A range slider with a constraint::
1029
1030
@interact
1031
def _(t = range_slider([1..1000], default=(100,200), label=r'Choose a range for $\alpha$')):
1032
print(t)
1033
"""
1034
kwds['range'] = True
1035
return slider(*args, **kwds)
1036
1037
def selector(values, label=None, default=None,
1038
nrows=None, ncols=None, width=None, buttons=False,
1039
button_classes=None):
1040
"""
1041
A drop down menu or a button bar for use in conjunction with
1042
the :func:`interact` command. We use the same command to
1043
create either a drop down menu or selector bar of buttons,
1044
since conceptually the two controls do exactly the same thing
1045
- they only look different. If either ``nrows`` or ``ncols``
1046
is given, then you get a buttons instead of a drop down menu.
1047
1048
INPUT:
1049
1050
- ``values`` - either (1) a list [val0, val1, val2, ...] or (2)
1051
a list of pairs [(val0, lbl0), (val1,lbl1), ...] in which case
1052
all labels must be given -- use None to auto-compute a given label.
1053
- ``label`` - a string (default: None); if given, this label
1054
is placed to the left of the entire button group
1055
- ``default`` - an object (default: first); default value in values list
1056
- ``nrows`` - an integer (default: None); if given determines
1057
the number of rows of buttons; if given, buttons=True
1058
- ``ncols`` - an integer (default: None); if given determines
1059
the number of columns of buttons; if given, buttons=True
1060
- ``width`` - an integer or string (default: None); if given,
1061
all buttons are this width. If an integer, the default units
1062
are 'ex'. A string that specifies any valid HTML units (e.g., '100px', '3em')
1063
is also allowed [SALVUS only].
1064
- ``buttons`` - a bool (default: False, except as noted
1065
above); if True, use buttons
1066
- ``button_classes`` - [SALVUS only] None, a string, or list of strings
1067
of the of same length as values, whose entries are a whitespace-separated
1068
string of CSS classes, e.g., Bootstrap CSS classes such as:
1069
btn-primary, btn-info, btn-success, btn-warning, btn-danger,
1070
btn-link, btn-large, btn-small, btn-mini.
1071
See http://twitter.github.com/bootstrap/base-css.html#buttons
1072
If button_classes a single string, that class is applied to all buttons.
1073
"""
1074
if (len(values) > 0 and isinstance(values[0], tuple) and len(values[0]) == 2):
1075
vals = [z[0] for z in values]
1076
lbls = [str(z[1]) if z[1] is not None else None for z in values]
1077
else:
1078
vals = values
1079
lbls = [None] * len(vals)
1080
1081
for i in range(len(vals)):
1082
if lbls[i] is None:
1083
v = vals[i]
1084
lbls[i] = v if isinstance(v, str) else str(v)
1085
1086
if default is None:
1087
default = 0
1088
else:
1089
try:
1090
default = vals.index(default)
1091
except IndexError:
1092
default = 0
1093
1094
opts = dict(locals())
1095
for k in ['vals', 'values', 'i', 'v', 'z']:
1096
if k in opts:
1097
del opts[k] # these could have a big jsonable repr
1098
1099
opts['lbls'] = lbls
1100
return control(
1101
control_type = 'selector',
1102
opts = opts,
1103
repr = "Selector labeled %r with values %s"%(label, values),
1104
convert_from_client = lambda n : vals[int(n)],
1105
convert_to_client = lambda x : vals.index(x)
1106
)
1107
1108
1109
1110
interact_functions = {}
1111
interact_controls = ['button', 'checkbox', 'color_selector', 'input_box',
1112
'range_slider', 'selector', 'slider', 'text_control',
1113
'input_grid']
1114
1115
for f in ['interact'] + interact_controls:
1116
interact_functions[f] = globals()[f]
1117
1118
# A little magic so that "interact.controls.[tab]" shows all the controls.
1119
class Controls:
1120
pass
1121
Interact.controls = Controls()
1122
for f in interact_controls:
1123
interact.controls.__dict__[f] = interact_functions[f]
1124
1125
1126
##########################################################################################
1127
# Cell object -- programatically control the current cell.
1128
##########################################################################################
1129
1130
1131
class Cell(object):
1132
def id(self):
1133
"""
1134
Return the UUID of the cell in which this function is called.
1135
"""
1136
return salvus._id
1137
1138
def hide(self, component='input'):
1139
"""
1140
Hide the 'input' or 'output' component of a cell.
1141
"""
1142
salvus.hide(component)
1143
1144
def show(self, component='input'):
1145
"""
1146
Show the 'input' or 'output' component of a cell.
1147
"""
1148
salvus.show(component)
1149
1150
def hideall(self):
1151
"""
1152
Hide the input and output fields of the cell in which this code executes.
1153
"""
1154
salvus.hide('input')
1155
salvus.hide('output')
1156
1157
#def input(self, val=None):
1158
# """
1159
# Get or set the value of the input component of the cell in
1160
# which this code executes.
1161
# """
1162
# salvus.javascript("cell.set_input(obj)", obj=val)
1163
#
1164
#def output(self, val=None):
1165
# """
1166
# Get or set the value of the output component of the cell in
1167
# which this code executes.
1168
# """
1169
# salvus.javascript("cell.set_output(obj)", obj=val)
1170
# return salvus.output(val, self._id)
1171
1172
cell = Cell()
1173
1174
##########################################################################################
1175
# Cell decorators -- aka "percent modes"
1176
##########################################################################################
1177
1178
import sage.misc.html
1179
try:
1180
_html = sage.misc.html.HTML()
1181
except:
1182
_html = sage.misc.html.HTMLFragmentFactory
1183
1184
class HTML:
1185
"""
1186
Cell mode that renders everything after %html as HTML
1187
1188
EXAMPLES::
1189
1190
---
1191
%html
1192
<h1>A Title</h1>
1193
<h2>Subtitle</h2>
1194
1195
---
1196
%html(hide=True)
1197
<h1>A Title</h1>
1198
<h2>Subtitle</h2>
1199
1200
---
1201
%html("<h1>A title</h1>", hide=False)
1202
1203
---
1204
%html(hide=False) <h1>Title</h1>
1205
1206
"""
1207
def __init__(self, hide=False):
1208
self._hide = hide
1209
1210
def __call__(self, *args, **kwds):
1211
if len(kwds) > 0 and len(args) == 0:
1212
return HTML(**kwds)
1213
if len(args) > 0:
1214
self._render(args[0], **kwds)
1215
1216
def _render(self, s, hide=None):
1217
if hide is None:
1218
hide = self._hide
1219
if hide:
1220
salvus.hide('input')
1221
salvus.html(s)
1222
1223
def table(self, rows = None, header=False):
1224
"""
1225
Renders a given matrix or nested list as an HTML table.
1226
1227
Arguments::
1228
1229
* **rows**: the rows of the table as a list of lists
1230
* **header**: if True, the first row is formatted as a header (default: False)
1231
"""
1232
# TODO: support columns as in http://doc.sagemath.org/html/en/reference/misc/sage/misc/table.html
1233
assert rows is not None, '"rows" is a mandatory argument, should be a list of lists'
1234
1235
from sage.matrix.matrix import is_Matrix
1236
import numpy as np
1237
1238
if is_Matrix(rows):
1239
table = list(rows) # list of Sage Vectors
1240
elif isinstance(rows, np.ndarray):
1241
table = rows.tolist()
1242
else:
1243
table = rows
1244
1245
assert isinstance(table, (tuple, list)), '"rows" must be a list of lists'
1246
1247
def as_unicode(s):
1248
'''
1249
This not only deals with unicode strings, but also converts e.g. `Integer` objects to a str
1250
'''
1251
if not isinstance(s, unicode):
1252
try:
1253
return unicode(s, 'utf8')
1254
except:
1255
return unicode(str(s), 'utf8')
1256
return s
1257
1258
def mk_row(row, header=False):
1259
is_vector = hasattr(row, 'is_vector') and row.is_vector()
1260
assert isinstance(row, (tuple, list)) or is_vector, '"rows" must contain lists or vectors for each row'
1261
tag = 'th' if header else 'td'
1262
row = [u'<{tag}>{}</{tag}>'.format(as_unicode(_), tag = tag) for _ in row]
1263
return u'<tr>{}</tr>'.format(u''.join(row))
1264
1265
thead = u'<thead>{}</thead>'.format(mk_row(table.pop(0), header=True)) if header else ''
1266
h_rows = [mk_row(row) for row in table]
1267
html_table = u'<table style="width: auto;" class="table table-bordered">{}<tbody>{}</tbody></table>'
1268
self(html_table.format(thead, ''.join(h_rows)))
1269
1270
html = HTML()
1271
html.iframe = _html.iframe # written in a way that works fine
1272
1273
def coffeescript(s=None, once=False):
1274
"""
1275
Execute code using CoffeeScript.
1276
1277
For example:
1278
1279
%coffeescript console.log 'hi'
1280
1281
or
1282
1283
coffeescript("console.log 'hi'")
1284
1285
You may either pass in a string or use this as a cell decorator,
1286
i.e., put %coffeescript at the top of a cell.
1287
1288
If you set once=False, the code will be executed every time the output of the cell is rendered, e.g.,
1289
on load, like with %auto::
1290
1291
coffeescript('console.log("hi")', once=False)
1292
1293
or
1294
1295
%coffeescript(once=False)
1296
console.log("hi")
1297
1298
1299
EXTRA FUNCTIONALITY:
1300
1301
When executing code, a function called print is defined, and objects cell and worksheet.::
1302
1303
print(1,2,'foo','bar') -- displays the inputs in the output cell
1304
1305
cell -- has attributes cell.output (the html output box) and cell.cell_id
1306
1307
worksheet -- has attributes project_page and editor, and methods interrupt, kill, and
1308
1309
execute_code: (opts) =>
1310
opts = defaults opts,
1311
code : required
1312
data : undefined
1313
preparse : true
1314
cb : undefined
1315
1316
OPTIMIZATION: When used alone as a cell decorator in a Sage worksheet
1317
with once=False (the default), rendering is done entirely client side,
1318
which is much faster, not requiring a round-trip to the server.
1319
"""
1320
if s is None:
1321
return lambda s : salvus.javascript(s, once=once, coffeescript=True)
1322
else:
1323
return salvus.javascript(s, coffeescript=True, once=once)
1324
1325
def javascript(s=None, once=False):
1326
"""
1327
Execute code using JavaScript.
1328
1329
For example:
1330
1331
%javascript console.log('hi')
1332
1333
or
1334
1335
javascript("console.log('hi')")
1336
1337
1338
You may either pass in a string or use this as a cell decorator,
1339
i.e., put %javascript at the top of a cell.
1340
1341
If once=False (the default), the code will be executed every time the output of the
1342
cell is rendered, e.g., on load, like with %auto::
1343
1344
javascript('.. some code ', once=False)
1345
1346
or
1347
1348
%javascript(once=False)
1349
... some code
1350
1351
WARNING: If once=True, then this code is likely to get executed *before* the rest
1352
of the output for this cell has been rendered by the client.
1353
1354
javascript('console.log("HI")', once=False)
1355
1356
EXTRA FUNCTIONALITY:
1357
1358
When executing code, a function called print is defined, and objects cell and worksheet.::
1359
1360
print(1,2,'foo','bar') -- displays the inputs in the output cell
1361
1362
cell -- has attributes cell.output (the html output box) and cell.cell_id
1363
1364
worksheet -- has attributes project_page and editor, and methods interrupt, kill, and
1365
1366
execute_code: (opts) =>
1367
opts = defaults opts,
1368
code : required
1369
data : undefined
1370
preparse : true
1371
cb : undefined
1372
1373
This example illustrates using worksheet.execute_code::
1374
1375
%coffeescript
1376
for i in [500..505]
1377
worksheet.execute_code
1378
code : "i=salvus.data['i']; i, factor(i)"
1379
data : {i:i}
1380
cb : (mesg) ->
1381
if mesg.stdout then print(mesg.stdout)
1382
if mesg.stderr then print(mesg.stderr)
1383
1384
OPTIMIZATION: When used alone as a cell decorator in a Sage worksheet
1385
with once=False (the default), rendering is done entirely client side,
1386
which is much faster, not requiring a round-trip to the server.
1387
"""
1388
if s is None:
1389
return lambda s : salvus.javascript(s, once=once)
1390
else:
1391
return salvus.javascript(s, once=once)
1392
1393
javascript_exec_doc = r"""
1394
1395
To send code from Javascript back to the Python process to
1396
be executed use the worksheet.execute_code function::
1397
1398
%javascript worksheet.execute_code(string_to_execute)
1399
1400
You may also use a more general call format of the form::
1401
1402
%javascript
1403
worksheet.execute_code({code:string_to_execute, data:jsonable_object,
1404
preparse:true or false, cb:function});
1405
1406
The data object is available when the string_to_execute is being
1407
evaluated as salvus.data. For example, if you execute this code
1408
in a cell::
1409
1410
javascript('''
1411
worksheet.execute_code({code:"a = salvus.data['b']/2; print(a)", data:{b:5},
1412
preparse:false, cb:function(mesg) { console.log(mesg)} });
1413
''')
1414
1415
then the Python variable a is set to 2, and the Javascript console log will display::
1416
1417
Object {done: false, event: "output", id: "..."}
1418
Object {stdout: "2\n", done: true, event: "output", id: "..."}
1419
1420
You can also send an interrupt signal to the Python process from
1421
Javascript by calling worksheet.interrupt(), and kill the process
1422
with worksheet.kill(). For example, here the a=4 never
1423
happens (but a=2 does)::
1424
1425
%javascript
1426
worksheet.execute_code({code:'a=2; sleep(100); a=4;',
1427
cb:function(mesg) { worksheet.interrupt(); console.log(mesg)}})
1428
1429
or using CoffeeScript (a Javascript preparser)::
1430
1431
%coffeescript
1432
worksheet.execute_code
1433
code : 'a=2; sleep(100); a=4;'
1434
cb : (mesg) ->
1435
worksheet.interrupt()
1436
console.log(mesg)
1437
1438
The Javascript code is evaluated with numerous standard Javascript libraries available,
1439
including jQuery, Twitter Bootstrap, jQueryUI, etc.
1440
1441
"""
1442
1443
for s in [coffeescript, javascript]:
1444
s.__doc__ += javascript_exec_doc
1445
1446
def latex0(s=None, **kwds):
1447
"""
1448
Create and display an arbitrary LaTeX document as a png image in the Salvus Notebook.
1449
1450
In addition to directly calling latex.eval, you may put %latex (or %latex.eval(density=75, ...etc...))
1451
at the top of a cell, which will typeset everything else in the cell.
1452
"""
1453
if s is None:
1454
return lambda t : latex0(t, **kwds)
1455
import os
1456
if 'filename' not in kwds:
1457
import tempfile
1458
delete_file = True
1459
kwds['filename'] = tempfile.mkstemp(suffix=".png")[1]
1460
else:
1461
delete_file = False
1462
if 'locals' not in kwds:
1463
kwds['locals'] = salvus.namespace
1464
if 'globals' not in kwds:
1465
kwds['globals'] = salvus.namespace
1466
sage.misc.latex.latex.add_package_to_preamble_if_available('soul')
1467
sage.misc.latex.Latex.eval(sage.misc.latex.latex, s, **kwds)
1468
salvus.file(kwds['filename'], once=False)
1469
if delete_file:
1470
os.unlink(kwds['filename'])
1471
return ''
1472
1473
latex0.__doc__ += sage.misc.latex.Latex.eval.__doc__
1474
1475
1476
class Time:
1477
"""
1478
Time execution of code exactly once in Salvus by:
1479
1480
- putting %time at the top of a cell to time execution of the entire cell
1481
- put %time at the beginning of line to time execution of just that line
1482
- write time('some code') to executation of the contents of the string.
1483
1484
If you want to time repeated execution of code for benchmarking purposes, use
1485
the timeit command instead.
1486
"""
1487
def __init__(self, start=False):
1488
if start:
1489
from sage.all import walltime, cputime
1490
self._start_walltime = walltime()
1491
self._start_cputime = cputime()
1492
1493
def before(self, code):
1494
return Time(start=True)
1495
1496
def after(self, code):
1497
from sage.all import walltime, cputime
1498
print("\nCPU time: %.2f s, Wall time: %.2f s" % (cputime(self._start_cputime), walltime(self._start_walltime)))
1499
self._start_cputime = self._start_walltime = None
1500
1501
def __call__(self, code):
1502
from sage.all import walltime, cputime
1503
not_as_decorator = self._start_cputime is None
1504
if not_as_decorator:
1505
self.before(code)
1506
salvus.execute(code)
1507
if not_as_decorator:
1508
self.after(code)
1509
1510
time = Time()
1511
1512
1513
def file(path):
1514
"""
1515
Block decorator to write to a file. Use as follows:
1516
1517
%file('filename') put this line in the file
1518
1519
or
1520
1521
%file('filename')
1522
everything in the rest of the
1523
cell goes into the file with given name.
1524
1525
1526
As with all block decorators in Salvus, the arguments to file can
1527
be arbitrary expressions. For examples,
1528
1529
a = 'file'; b = ['name', 'txt']
1530
1531
%file(a+b[0]+'.'+b[1]) rest of line goes in 'filename.txt'
1532
"""
1533
return lambda content: open(path,'w').write(content)
1534
1535
1536
def timeit(*args, **kwds):
1537
"""
1538
Time execution of a command or block of commands.
1539
1540
This command has been enhanced for Salvus so you may use it as
1541
a block decorator as well, e.g.,
1542
1543
%timeit 2+3
1544
1545
and
1546
1547
%timeit(number=10, preparse=False) 2^3
1548
1549
%timeit(number=10, seconds=True) 2^3
1550
1551
and
1552
1553
%timeit(preparse=False)
1554
1555
[rest of the cell]
1556
1557
Here is the original docstring for timeit:
1558
1559
"""
1560
def go(code):
1561
print(sage.misc.sage_timeit.sage_timeit(code, globals_dict=salvus.namespace, **kwds))
1562
if len(args) == 0:
1563
return lambda code : go(code)
1564
else:
1565
go(*args)
1566
1567
# TODO: these need to also give the argspec
1568
timeit.__doc__ += sage.misc.sage_timeit.sage_timeit.__doc__
1569
1570
1571
class Capture:
1572
"""
1573
Capture or ignore the output from evaluating the given code. (SALVUS only).
1574
1575
Use capture as a block decorator by placing either %capture or
1576
%capture(optional args) at the beginning of a cell or at the
1577
beginning of a line. If you use just plain %capture then stdout
1578
and stderr are completely ignored. If you use %capture(args)
1579
you can redirect or echo stdout and stderr to variables or
1580
files. For example if you start a cell with this line::
1581
1582
%capture(stdout='output', stderr=open('error','w'), append=True, echo=True)
1583
1584
then stdout is appended (because append=True) to the global
1585
variable output, stderr is written to the file 'error', and the
1586
output is still displayed in the output portion of the cell (echo=True).
1587
1588
INPUT:
1589
1590
- stdout -- string (or object with write method) to send stdout output to (string=name of variable)
1591
- stderr -- string (or object with write method) to send stderr output to (string=name of variable)
1592
- append -- (default: False) if stdout/stderr are a string, append to corresponding variable
1593
- echo -- (default: False) if True, also echo stdout/stderr to the output cell.
1594
"""
1595
def __init__(self, stdout, stderr, append, echo):
1596
self.v = (stdout, stderr, append, echo)
1597
1598
def before(self, code):
1599
(stdout, stderr, append, echo) = self.v
1600
self._orig_stdout_f = orig_stdout_f = sys.stdout._f
1601
if stdout is not None:
1602
if hasattr(stdout, 'write'):
1603
def write_stdout(buf):
1604
stdout.write(buf)
1605
elif isinstance(stdout, str):
1606
if (stdout not in salvus.namespace) or not append:
1607
salvus.namespace[stdout] = ''
1608
if not isinstance(salvus.namespace[stdout], str):
1609
salvus.namespace[stdout] = str(salvus.namespace[stdout])
1610
def write_stdout(buf):
1611
salvus.namespace[stdout] += buf
1612
else:
1613
raise TypeError("stdout must be None, a string, or have a write method")
1614
def f(buf, done):
1615
write_stdout(buf)
1616
if echo:
1617
orig_stdout_f(buf, done)
1618
elif done:
1619
orig_stdout_f('', done)
1620
sys.stdout._f = f
1621
elif not echo:
1622
def f(buf,done):
1623
if done:
1624
orig_stdout_f('',done)
1625
sys.stdout._f = f
1626
1627
self._orig_stderr_f = orig_stderr_f = sys.stderr._f
1628
if stderr is not None:
1629
if hasattr(stderr, 'write'):
1630
def write_stderr(buf):
1631
stderr.write(buf)
1632
elif isinstance(stderr, str):
1633
if (stderr not in salvus.namespace) or not append:
1634
salvus.namespace[stderr] = ''
1635
if not isinstance(salvus.namespace[stderr], str):
1636
salvus.namespace[stderr] = str(salvus.namespace[stderr])
1637
def write_stderr(buf):
1638
salvus.namespace[stderr] += buf
1639
else:
1640
raise TypeError("stderr must be None, a string, or have a write method")
1641
def f(buf, done):
1642
write_stderr(buf)
1643
if echo:
1644
orig_stderr_f(buf, done)
1645
elif done:
1646
orig_stderr_f('', done)
1647
sys.stderr._f = f
1648
elif not echo:
1649
def f(buf,done):
1650
if done:
1651
orig_stderr_f('',done)
1652
sys.stderr._f = f
1653
1654
1655
return self
1656
1657
def __call__(self, code=None, stdout=None, stderr=None, append=False, echo=False):
1658
if code is None:
1659
return Capture(stdout=stdout, stderr=stderr, append=append, echo=echo)
1660
if salvus._prefix:
1661
if not code.startswith("%"):
1662
code = salvus._prefix + '\n' + code
1663
salvus.execute(code)
1664
1665
1666
def after(self, code):
1667
sys.stdout._f = self._orig_stdout_f
1668
sys.stderr._f = self._orig_stderr_f
1669
1670
1671
capture = Capture(stdout=None, stderr=None, append=False, echo=False)
1672
1673
import sage.misc.cython
1674
1675
def cython(code=None, **kwds):
1676
"""
1677
Block decorator to easily include Cython code in CoCalc worksheets.
1678
1679
Put %cython at the top of a cell, and the rest of that cell is compiled as
1680
Cython code and made directly available to use in other cells.
1681
1682
You can pass options to cython by typing "%cython(... var=value...)"
1683
instead of just "%cython".
1684
1685
If you give the option silent=True (not the default) then this won't
1686
print what functions get globally defined as a result of evaluating code.
1687
1688
This is a wrapper around Sage's own cython function, whose
1689
docstring is below:
1690
1691
ORIGINAL DOCSTRING:
1692
1693
"""
1694
if code is None:
1695
return lambda code: cython(code, **kwds)
1696
from sage.misc.temporary_file import tmp_dir
1697
path = tmp_dir()
1698
filename = os.path.join(path, 'a.pyx')
1699
open(filename, 'w').write(code)
1700
1701
silent = kwds.get('silent', False)
1702
if 'silent' in kwds:
1703
del kwds['silent']
1704
1705
if 'annotate' not in kwds and not silent:
1706
kwds['annotate'] = True
1707
1708
modname, path = sage.misc.cython.cython(filename, **kwds)
1709
1710
try:
1711
sys.path.insert(0,path)
1712
module = __import__(modname)
1713
finally:
1714
del sys.path[0]
1715
1716
import inspect
1717
defined = []
1718
for name, value in inspect.getmembers(module):
1719
if not name.startswith('_') and name != 'init_memory_functions':
1720
salvus.namespace[name] = value
1721
defined.append(name)
1722
if not silent:
1723
if defined:
1724
print("Defined %s" % (', '.join(defined)))
1725
else:
1726
print("No functions defined.")
1727
1728
files = os.listdir(path)
1729
html_filename = None
1730
for n in files:
1731
base, ext = os.path.splitext(n)
1732
if ext.startswith('.html') and '_pyx_' in base:
1733
html_filename = os.path.join(path, n)
1734
if html_filename is not None:
1735
salvus.file(html_filename, raw=True, show=True, text="Auto-generated code...")
1736
1737
cython.__doc__ += sage.misc.cython.cython.__doc__
1738
1739
1740
class script:
1741
r"""
1742
Block decorator to run an arbitrary shell command with input from a
1743
cell in Salvus.
1744
1745
Put %script('shell command line') or %script(['command', 'arg1',
1746
'arg2', ...]) by itself on a line in a cell, and the command line
1747
is run with stdin the rest of the contents of the cell. You can
1748
also use script in single line mode, e.g.,::
1749
1750
%script('gp -q') factor(2^97 - 1)
1751
1752
or
1753
1754
%script(['gp', '-q']) factor(2^97 - 1)
1755
1756
will launch a gp session, feed 'factor(2^97-1)' into stdin, and
1757
display the resulting factorization.
1758
1759
NOTE: the result is stored in the attribute "stdout", so you can do::
1760
1761
s = script('gp -q')
1762
%s factor(2^97-1)
1763
s.stdout
1764
'\n[11447 1]\n\n[13842607235828485645766393 1]\n\n'
1765
1766
and s.stdout will now be the output string.
1767
1768
You may also specify the shell environment with the env keyword.
1769
"""
1770
def __init__(self, args, env=None):
1771
self._args = args
1772
self._env = env
1773
def __call__(self, code=''):
1774
import subprocess
1775
try:
1776
s = None
1777
s = subprocess.Popen(self._args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1778
stderr=subprocess.STDOUT, shell=isinstance(self._args, str),
1779
env=self._env)
1780
s.stdin.write(code); s.stdin.close()
1781
finally:
1782
if s is None:
1783
return
1784
try:
1785
self.stdout = s.stdout.read()
1786
sys.stdout.write(self.stdout)
1787
finally:
1788
try:
1789
os.system("pkill -TERM -P %s"%s.pid)
1790
except OSError:
1791
pass
1792
try:
1793
os.kill(s.pid, 9)
1794
except OSError:
1795
pass
1796
1797
def python(code):
1798
"""
1799
Block decorator to run code in pure Python mode, without it being
1800
preparsed by the Sage preparser. Otherwise, nothing changes.
1801
1802
To use this, put %python by itself in a cell so that it applies to
1803
the rest of the cell, or put it at the beginning of a line to
1804
disable preparsing just for that line.
1805
"""
1806
salvus.execute(code, preparse=False)
1807
1808
def python3(code=None,**kwargs):
1809
"""
1810
Block decorator to run code in a pure Python3 mode session.
1811
1812
To use this, put %python3 by itself in a cell so that it applies to
1813
the rest of the cell, or put it at the beginning of a line to
1814
run just that line using python3.
1815
1816
You can combine %python3 with capture, if you would like to capture
1817
the output to a variable. For example::
1818
1819
%capture(stdout='p3')
1820
%python3
1821
x = set([1,2,3])
1822
print(x)
1823
1824
Afterwards, p3 contains the output '{1, 2, 3}' and the variable x
1825
in the controlling Sage session is in no way impacted.
1826
1827
.. note::
1828
1829
State is preserved between cells.
1830
SMC %python3 mode uses the jupyter `anaconda3` kernel.
1831
"""
1832
if python3.jupyter_kernel is None:
1833
python3.jupyter_kernel = jupyter("anaconda3")
1834
return python3.jupyter_kernel(code,**kwargs)
1835
python3.jupyter_kernel = None
1836
1837
def singular_kernel(code=None,**kwargs):
1838
"""
1839
Block decorator to run code in a Singular mode session.
1840
1841
To use this, put %singular_kernel by itself in a cell so that it applies to
1842
the rest of the cell, or put it at the beginning of a line to
1843
run just that line using singular_kernel.
1844
1845
State is preserved between cells.
1846
1847
This is completely different than the singular command in Sage itself, which
1848
supports things like x = singular(sage_object), and *also* provides a way
1849
to execute code by beginning cells with %singular. The singular interface in
1850
Sage uses pexpect, so might be less robust than singular_kernel.
1851
1852
.. note::
1853
1854
SMC %singular_kernel mode uses the jupyter `singular` kernel:
1855
https://github.com/sebasguts/jupyter_kernel_singular
1856
"""
1857
if singular_kernel.jupyter_kernel is None:
1858
singular_kernel.jupyter_kernel = jupyter("singular")
1859
return singular_kernel.jupyter_kernel(code,**kwargs)
1860
singular_kernel.jupyter_kernel = None
1861
1862
def perl(code):
1863
"""
1864
Block decorator to run code in a Perl session.
1865
1866
To use this, put %perl by itself in a cell so that it applies to
1867
the rest of the cell, or put it at the beginning of a line to
1868
run just that line using perl.
1869
1870
EXAMPLE:
1871
1872
A perl cell::
1873
1874
%perl
1875
$apple_count = 5;
1876
$count_report = "There are $apple_count apples.";
1877
print "The report is: $count_report\n";
1878
1879
Or use %perl on one line::
1880
1881
%perl $apple_count = 5; $count_report = "There are $apple_count apples."; print "The report is: $count_report\n";
1882
1883
You can combine %perl with capture, if you would like to capture
1884
the output to a variable. For example::
1885
1886
%capture(stdout='p')
1887
%perl print "hi"
1888
1889
Afterwards, p contains 'hi'.
1890
1891
NOTE: No state is preserved between calls. Each call is a separate process.
1892
"""
1893
script('sage-native-execute perl')(code)
1894
1895
1896
def ruby(code):
1897
"""
1898
Block decorator to run code in a Ruby session.
1899
1900
To use this, put %ruby by itself in a cell so that it applies to
1901
the rest of the cell, or put it at the beginning of a line to
1902
run just that line using ruby.
1903
1904
EXAMPLE:
1905
1906
A ruby cell::
1907
1908
%ruby
1909
lang = "ruby"
1910
print "Hello from #{lang}!"
1911
1912
Or use %ruby on one line::
1913
1914
%ruby lang = "ruby"; print "Hello from #{lang}!"
1915
1916
You can combine %ruby with capture, if you would like to capture
1917
the output to a variable. For example::
1918
1919
%capture(stdout='p')
1920
%ruby lang = "ruby"; print "Hello from #{lang}!"
1921
1922
Afterwards, p contains 'Hello from ruby!'.
1923
1924
NOTE: No state is preserved between calls. Each call is a separate process.
1925
"""
1926
script('sage-native-execute ruby')(code)
1927
1928
1929
def fortran(x, library_paths=[], libraries=[], verbose=False):
1930
"""
1931
Compile Fortran code and make it available to use.
1932
1933
INPUT:
1934
1935
- x -- a string containing code
1936
1937
Use this as a decorator. For example, put this in a cell and evaluate it::
1938
1939
%fortran
1940
1941
C FILE: FIB1.F
1942
SUBROUTINE FIB(A,N)
1943
C
1944
C CALCULATE FIRST N FIBONACCI NUMBERS
1945
C
1946
INTEGER N
1947
REAL*8 A(N)
1948
DO I=1,N
1949
IF (I.EQ.1) THEN
1950
A(I) = 0.0D0
1951
ELSEIF (I.EQ.2) THEN
1952
A(I) = 1.0D0
1953
ELSE
1954
A(I) = A(I-1) + A(I-2)
1955
ENDIF
1956
ENDDO
1957
END
1958
C END FILE FIB1.F
1959
1960
1961
In the next cell, evaluate this::
1962
1963
import numpy
1964
n = numpy.array(range(10),dtype=float)
1965
fib(n,int(10))
1966
n
1967
1968
This will produce this output: array([ 0., 1., 1., 2., 3., 5., 8., 13., 21., 34.])
1969
"""
1970
import __builtin__
1971
from sage.misc.temporary_file import tmp_dir
1972
if len(x.splitlines()) == 1 and os.path.exists(x):
1973
filename = x
1974
x = open(x).read()
1975
if filename.lower().endswith('.f90'):
1976
x = '!f90\n' + x
1977
1978
from numpy import f2py
1979
from random import randint
1980
1981
# Create everything in a temporary directory
1982
mytmpdir = tmp_dir()
1983
1984
try:
1985
old_cwd = os.getcwd()
1986
os.chdir(mytmpdir)
1987
1988
old_import_path = os.sys.path
1989
os.sys.path.append(mytmpdir)
1990
1991
name = "fortran_module_%s"%randint(0,2**64) # Python module name
1992
# if the first line has !f90 as a comment, gfortran will
1993
# treat it as Fortran 90 code
1994
if x.startswith('!f90'):
1995
fortran_file = name + '.f90'
1996
else:
1997
fortran_file = name + '.f'
1998
1999
s_lib_path = ""
2000
s_lib = ""
2001
for s in library_paths:
2002
s_lib_path = s_lib_path + "-L%s "
2003
2004
for s in libraries:
2005
s_lib = s_lib + "-l%s "%s
2006
2007
log = name + ".log"
2008
extra_args = '--quiet --f77exec=sage-inline-fortran --f90exec=sage-inline-fortran %s %s >"%s" 2>&1'%(
2009
s_lib_path, s_lib, log)
2010
2011
f2py.compile(x, name, extra_args = extra_args, source_fn=fortran_file)
2012
log_string = open(log).read()
2013
2014
# f2py.compile() doesn't raise any exception if it fails.
2015
# So we manually check whether the compiled file exists.
2016
# NOTE: the .so extension is used expect on Cygwin,
2017
# that is even on OS X where .dylib might be expected.
2018
soname = name
2019
uname = os.uname()[0].lower()
2020
if uname[:6] == "cygwin":
2021
soname += '.dll'
2022
else:
2023
soname += '.so'
2024
if not os.path.isfile(soname):
2025
raise RuntimeError("failed to compile Fortran code:\n" + log_string)
2026
2027
if verbose:
2028
print(log_string)
2029
2030
m = __builtin__.__import__(name)
2031
2032
finally:
2033
os.sys.path = old_import_path
2034
os.chdir(old_cwd)
2035
try:
2036
import shutil
2037
shutil.rmtree(mytmpdir)
2038
except OSError:
2039
# This can fail for example over NFS
2040
pass
2041
2042
for k, x in m.__dict__.iteritems():
2043
if k[0] != '_':
2044
salvus.namespace[k] = x
2045
2046
def sh(code=None,**kwargs):
2047
"""
2048
Run a bash script in Salvus. Uses jupyter bash kernel
2049
which allows keeping state between cells.
2050
2051
EXAMPLES:
2052
2053
Use as a block decorator on a single line::
2054
2055
%sh pwd
2056
2057
and multiline
2058
2059
%sh
2060
echo "hi"
2061
pwd
2062
ls -l
2063
2064
You can also just directly call it::
2065
2066
sh('pwd')
2067
2068
The output is printed. To capture it, use capture
2069
2070
%capture(stdout='output')
2071
%sh pwd
2072
2073
After that, the variable output contains the current directory
2074
2075
Remember shell state between cells
2076
2077
%sh
2078
FOO='xyz'
2079
cd /tmp
2080
... new cell will show settings from previous cell ...
2081
%sh
2082
echo $FOO
2083
pwd
2084
2085
Display image file (this is a feature of jupyter bash kernel)
2086
2087
%sh
2088
display < sage_logo.png
2089
2090
.. WARNING::
2091
2092
The jupyter bash kernel does not separate stdout and stderr as cell is running.
2093
It only returns ok or error depending on exit status of last command in the cell.
2094
So all cell output captured goes to either stdout or stderr variable, depending
2095
on exit status of the last command in the %sh cell.
2096
"""
2097
if sh.jupyter_kernel is None:
2098
sh.jupyter_kernel = jupyter("bash")
2099
sh.jupyter_kernel('function command_not_found_handle { printf "%s: command not found\n" "$1" >&2; return 127;}')
2100
return sh.jupyter_kernel(code,**kwargs)
2101
sh.jupyter_kernel = None
2102
2103
# use jupyter kernel for GNU octave instead of sage interpreter interface
2104
def octave(code=None,**kwargs):
2105
r"""
2106
Run GNU Octave code in a sage worksheet.
2107
2108
INPUT:
2109
2110
- ``code`` -- a string containing code
2111
2112
Use as a decorator. For example, put this in a cell and evaluate it::
2113
2114
%octave
2115
x = -10:0.1:10;
2116
plot (x, sin (x))
2117
2118
.. note::
2119
2120
SMC %octave mode uses the jupyter `octave` kernel.
2121
"""
2122
if octave.jupyter_kernel is None:
2123
octave.jupyter_kernel = jupyter("octave")
2124
octave.jupyter_kernel.smc_image_scaling = 1
2125
return octave.jupyter_kernel(code,**kwargs)
2126
octave.jupyter_kernel = None
2127
2128
# jupyter kernel for %ir mode
2129
def r(code=None,**kwargs):
2130
r"""
2131
Run R code in a sage worksheet.
2132
2133
INPUT:
2134
2135
- ``code`` -- a string containing code
2136
2137
Use as a decorator. For example, put this in a cell and evaluate it to see a scatter plot
2138
of built-in mtcars dataframe variables `mpg` vs `wt`::
2139
2140
%r
2141
with(mtcars,plot(wt,mpg))
2142
2143
.. note::
2144
2145
SMC %r mode uses the jupyter `ir` kernel.
2146
"""
2147
if r.jupyter_kernel is None:
2148
r.jupyter_kernel = jupyter("ir")
2149
r.jupyter_kernel('options(repr.plot.res = 240)')
2150
r.jupyter_kernel.smc_image_scaling = .5
2151
return r.jupyter_kernel(code,**kwargs)
2152
r.jupyter_kernel = None
2153
2154
# jupyter kernel for %scala mode
2155
def scala211(code=None,**kwargs):
2156
r"""
2157
Run scala code in a sage worksheet.
2158
2159
INPUT:
2160
2161
- ``code`` -- a string containing code
2162
2163
Use as a decorator.
2164
2165
.. note::
2166
2167
SMC %scala211 mode uses the jupyter `scala211` kernel.
2168
"""
2169
if scala211.jupyter_kernel is None:
2170
scala211.jupyter_kernel = jupyter("scala211")
2171
return scala211.jupyter_kernel(code,**kwargs)
2172
scala211.jupyter_kernel = None
2173
# add alias for generic scala
2174
scala = scala211
2175
2176
def prun(code):
2177
"""
2178
Use %prun followed by a block of code to profile execution of that
2179
code. This will display the resulting profile, along with a menu
2180
to select how to sort the data.
2181
2182
EXAMPLES:
2183
2184
Profile computing a tricky integral (on a single line)::
2185
2186
%prun integrate(sin(x^2),x)
2187
2188
Profile a block of code::
2189
2190
%prun
2191
E = EllipticCurve([1..5])
2192
v = E.anlist(10^5)
2193
r = E.rank()
2194
"""
2195
import cProfile, pstats
2196
from sage.misc.all import tmp_filename
2197
2198
filename = tmp_filename()
2199
cProfile.runctx(salvus.namespace['preparse'](code), salvus.namespace, locals(), filename)
2200
2201
@interact
2202
def f(title = text_control('', "<h1>Salvus Profiler</h1>"),
2203
sort=("First sort by", selector([('calls', 'number of calls to the function'),
2204
('time', ' total time spent in the function'),
2205
('cumulative', 'total time spent in this and all subfunctions (from invocation till exit)'),
2206
('module', 'name of the module that contains the function'),
2207
('name', 'name of the function')
2208
], width="100%", default='time')),
2209
strip_dirs=True):
2210
try:
2211
p = pstats.Stats(filename)
2212
if strip_dirs:
2213
p.strip_dirs()
2214
p.sort_stats(sort)
2215
p.print_stats()
2216
except Exception as msg:
2217
print(msg)
2218
2219
2220
2221
##############################################################
2222
# The %fork cell decorator.
2223
##############################################################
2224
2225
def _wait_in_thread(pid, callback, filename):
2226
from sage.structure.sage_object import load
2227
def wait():
2228
try:
2229
os.waitpid(pid,0)
2230
callback(load(filename))
2231
except Exception as msg:
2232
callback(msg)
2233
2234
from threading import Thread
2235
t = Thread(target=wait, args=tuple([]))
2236
t.start()
2237
2238
def async(f, args, kwds, callback):
2239
"""
2240
Run f in a forked subprocess with given args and kwds, then call the
2241
callback function when f terminates.
2242
"""
2243
from sage.misc.all import tmp_filename
2244
filename = tmp_filename() + '.sobj'
2245
sys.stdout.flush()
2246
sys.stderr.flush()
2247
pid = os.fork()
2248
if pid:
2249
# The parent master process
2250
try:
2251
_wait_in_thread(pid, callback, filename)
2252
return pid
2253
finally:
2254
if os.path.exists(filename):
2255
os.unlink(filename)
2256
else:
2257
# The child process
2258
try:
2259
result = f(*args, **kwds)
2260
except Exception as msg:
2261
result = str(msg)
2262
from sage.structure.sage_object import save
2263
save(result, filename)
2264
os._exit(0)
2265
2266
2267
class Fork(object):
2268
"""
2269
The %fork block decorator evaluates its code in a forked subprocess
2270
that does not block the main process.
2271
2272
You may still use the @fork function decorator from Sage, as usual,
2273
to run a function in a subprocess. Type "sage.all.fork?" to see
2274
the help for the @fork decorator.
2275
2276
WARNING: This is highly experimental and possibly flaky. Use with
2277
caution.
2278
2279
All (picklelable) global variables that are set in the forked
2280
subprocess are set in the parent when the forked subprocess
2281
terminates. However, the forked subprocess has no other side
2282
effects, except what it might do to file handles and the
2283
filesystem.
2284
2285
To see currently running forked subprocesses, type
2286
fork.children(), which returns a dictionary {pid:execute_uuid}.
2287
To kill a given subprocess and stop the cell waiting for input,
2288
type fork.kill(pid). This is currently the only way to stop code
2289
running in %fork cells.
2290
2291
TODO/WARNING: The subprocesses spawned by fork are not killed
2292
if the parent process is killed first!
2293
2294
NOTE: All pexpect interfaces are reset in the child process.
2295
"""
2296
def __init__(self):
2297
self._children = {}
2298
2299
def children(self):
2300
return dict(self._children)
2301
2302
def __call__(self, s):
2303
2304
if isinstance(s, types.FunctionType): # check for decorator usage
2305
import sage.parallel.decorate
2306
return sage.parallel.decorate.fork(s)
2307
2308
salvus._done = False
2309
2310
id = salvus._id
2311
2312
changed_vars = set([])
2313
2314
def change(var, val):
2315
changed_vars.add(var)
2316
2317
def f():
2318
# Run some commands to tell Sage that its
2319
# pid has changed.
2320
import sage.misc.misc
2321
reload(sage.misc.misc)
2322
2323
# The pexpect interfaces (and objects defined in them) are
2324
# not valid.
2325
sage.interfaces.quit.invalidate_all()
2326
2327
salvus.namespace.on('change', None, change)
2328
salvus.execute(s)
2329
result = {}
2330
from sage.structure.sage_object import dumps
2331
for var in changed_vars:
2332
try:
2333
result[var] = dumps(salvus.namespace[var])
2334
except:
2335
result[var] = 'unable to pickle %s'%var
2336
return result
2337
2338
2339
from sage.structure.sage_object import loads
2340
def g(s):
2341
if isinstance(s, Exception):
2342
sys.stderr.write(str(s))
2343
sys.stderr.flush()
2344
else:
2345
for var, val in s.iteritems():
2346
try:
2347
salvus.namespace[var] = loads(val)
2348
except:
2349
print("unable to unpickle %s" % var)
2350
salvus._conn.send_json({'event':'output', 'id':id, 'done':True})
2351
if pid in self._children:
2352
del self._children[pid]
2353
2354
pid = async(f, tuple([]), {}, g)
2355
print("Forked subprocess %s" % pid)
2356
self._children[pid] = id
2357
2358
def kill(self, pid):
2359
if pid in self._children:
2360
salvus._conn.send_json({'event':'output', 'id':self._children[pid], 'done':True})
2361
os.kill(pid, 9)
2362
del self._children[pid]
2363
else:
2364
raise ValueError("Unknown pid = (%s)" % pid)
2365
2366
fork = Fork()
2367
2368
2369
####################################################
2370
# Display of 2d/3d graphics objects
2371
####################################################
2372
2373
from sage.misc.all import tmp_filename
2374
from sage.plot.animate import Animation
2375
import matplotlib.figure
2376
2377
def show_animation(obj, delay=20, gif=False, **kwds):
2378
if gif:
2379
t = tmp_filename(ext='.gif')
2380
obj.gif(delay, t, **kwds)
2381
salvus.file(t, raw=False)
2382
os.unlink(t)
2383
else:
2384
t = tmp_filename(ext='.webm')
2385
obj.ffmpeg(t, delay=delay, **kwds)
2386
salvus.file(t, raw=True) # and let delete when worksheet ends - need this so can replay video.
2387
2388
def show_2d_plot_using_matplotlib(obj, svg, **kwds):
2389
if isinstance(obj, matplotlib.image.AxesImage):
2390
# The result of imshow, e.g.,
2391
#
2392
# from matplotlib import numpy, pyplot
2393
# pyplot.imshow(numpy.random.random_integers(255, size=(100,100,3)))
2394
#
2395
t = tmp_filename(ext='.png')
2396
obj.write_png(t)
2397
salvus.file(t)
2398
os.unlink(t)
2399
return
2400
2401
if isinstance(obj, matplotlib.axes.Axes):
2402
obj = obj.get_figure()
2403
2404
if 'events' in kwds:
2405
from graphics import InteractiveGraphics
2406
ig = InteractiveGraphics(obj, **kwds['events'])
2407
n = '__a'+uuid().replace('-','') # so it doesn't get garbage collected instantly.
2408
obj.__setattr__(n, ig)
2409
kwds2 = dict(kwds)
2410
del kwds2['events']
2411
ig.show(**kwds2)
2412
else:
2413
t = tmp_filename(ext = '.svg' if svg else '.png')
2414
if isinstance(obj, matplotlib.figure.Figure):
2415
obj.savefig(t, **kwds)
2416
else:
2417
obj.save(t, **kwds)
2418
salvus.file(t)
2419
os.unlink(t)
2420
2421
def show_3d_plot_using_tachyon(obj, **kwds):
2422
t = tmp_filename(ext = '.png')
2423
obj.save(t, **kwds)
2424
salvus.file(t)
2425
os.unlink(t)
2426
2427
def show_graph_using_d3(obj, **kwds):
2428
salvus.d3_graph(obj, **kwds)
2429
2430
2431
def plot3d_using_matplotlib(expr, rangeX, rangeY,
2432
density=40, elev=45., azim=35.,
2433
alpha=0.85, cmap=None):
2434
"""
2435
Plots a symbolic expression in two variables on a two dimensional grid
2436
and renders the function using matplotlib's 3D projection.
2437
The purpose is to make it possible to create vectorized images (PDF, SVG)
2438
for high-resolution images in publications -- instead of rasterized image formats.
2439
2440
Example::
2441
%var x y
2442
plot3d_using_matplotlib(x^2 + (1-y^2), (x, -5, 5), (y, -5, 5))
2443
2444
Arguments::
2445
2446
* expr: symbolic expression, e.g. x^2 - (1-y)^2
2447
* rangeX: triple: (variable, minimum, maximum), e.g. (x, -10, 10)
2448
* rangeY: like rangeX
2449
* density: grid density
2450
* elev: elevation, e.g. 45
2451
* azim: azimuth, e.g. 35
2452
* alpha: alpha transparency of plot (default: 0.85)
2453
* cmap: matplotlib colormap, e.g. matplotlib.cm.Blues (default)
2454
"""
2455
from matplotlib import cm
2456
import matplotlib.pyplot as plt
2457
from mpl_toolkits.mplot3d import axes3d
2458
import numpy as np
2459
2460
cmap = cmap or cm.Blues
2461
2462
plt.cla()
2463
fig = plt.figure()
2464
ax = fig.gca(projection='3d')
2465
ax.view_init(elev=elev, azim=azim)
2466
2467
xx = np.linspace(rangeX[1], rangeX[2], density)
2468
yy = np.linspace(rangeY[1], rangeY[2], density)
2469
X, Y = np.meshgrid(xx, yy)
2470
2471
import numpy as np
2472
exprv = np.vectorize(lambda x1, x2 : \
2473
float(expr.subs({rangeX[0] : x1, rangeY[0] : x2})))
2474
Z = exprv(X, Y)
2475
zlim = np.min(Z), np.max(Z)
2476
2477
ax.plot_surface(X, Y, Z, alpha=alpha, cmap=cmap, linewidth=.5,
2478
shade=True,
2479
rstride=int(len(xx)/10),
2480
cstride=int(len(yy)/10))
2481
2482
ax.set_xlabel('X')
2483
ax.set_xlim(*rangeX[1:])
2484
ax.set_ylabel('Y')
2485
ax.set_ylim(*rangeY[1:])
2486
ax.set_zlabel('Z')
2487
ax.set_zlim(*zlim)
2488
2489
plt.show()
2490
2491
2492
from sage.plot.graphics import Graphics, GraphicsArray
2493
from sage.plot.plot3d.base import Graphics3d
2494
from sage.plot.plot3d.tachyon import Tachyon
2495
import cgi
2496
2497
def show(*objs, **kwds):
2498
"""
2499
Show a 2d or 3d graphics object (or objects), animation, or matplotlib figure, or show an
2500
expression typeset nicely using LaTeX.
2501
2502
- display: (default: True); if True, use display math for expression (big and centered).
2503
2504
- svg: (default: True); if True, show 2d plots using svg (otherwise use png)
2505
2506
- d3: (default: True); if True, show graphs (vertices and edges) using an interactive D3 viewer
2507
for the many options for this viewer, type
2508
2509
import smc_sagews.graphics
2510
smc_sagews.graphics.graph_to_d3_jsonable?
2511
2512
If false, graphs are converted to plots and displayed as usual.
2513
2514
- renderer: (default: 'webgl'); for 3d graphics
2515
- 'webgl' (fastest) using hardware accelerated 3d;
2516
- 'canvas' (slower) using a 2d canvas, but may work better with transparency;
2517
- 'tachyon' -- a ray traced static image.
2518
2519
- spin: (default: False); spins 3d plot, with number determining speed (requires mouse over plot)
2520
2521
- events: if given, {'click':foo, 'mousemove':bar}; each time the user clicks,
2522
the function foo is called with a 2-tuple (x,y) where they clicked. Similarly
2523
for mousemove. This works for Sage 2d graphics and matplotlib figures.
2524
2525
- viewer: optional string, set to "tachyon" for static ray-tracing view of 3d image
2526
2527
- background: string (default: 'transparent'), specifies background color for 3d images.
2528
Ignored if viewer is set to 'tachyon' or if object type is Tachyon.
2529
May be 'transparent' or any valid CSS color string, e.g.: 'red', '#00ff00', 'rgb(0,0,255)'.
2530
2531
- foreground: string, specifies frame color for 3d images. Defaults to 'gray' when
2532
background is 'transparent', otherwise default is computed for visibility based on canvas
2533
background.
2534
2535
ANIMATIONS:
2536
2537
- animations are by default encoded and displayed using an efficiently web-friendly
2538
format (currently webm, which is **not supported** by Safari or IE).
2539
2540
- ``delay`` - integer (default: 20); delay in hundredths of a
2541
second between frames.
2542
2543
- gif=False -- if you set gif=True, instead use an animated gif,
2544
which is much less efficient, but works on all browsers.
2545
2546
You can also use options directly to the animate command, e.g., the figsize option below:
2547
2548
a = animate([plot(sin(x + a), (x, 0, 2*pi)) for a in [0, pi/4, .., 2*pi]], figsize=6)
2549
show(a, delay=30)
2550
2551
2552
EXAMPLES:
2553
2554
Some examples:
2555
2556
show(2/3)
2557
show([1, 4/5, pi^2 + e], 1+pi)
2558
show(x^2, display=False)
2559
show(e, plot(sin))
2560
2561
Here's an example that illustrates creating a clickable image with events::
2562
2563
@interact
2564
def f0(fun=x*sin(x^2), mousemove='', click='(0,0)'):
2565
click = sage_eval(click)
2566
g = plot(fun, (x,0,5), zorder=0) + point(click, color='red', pointsize=100, zorder=10)
2567
ymax = g.ymax(); ymin = g.ymin()
2568
m = fun.derivative(x)(x=click[0])
2569
b = fun(x=click[0]) - m*click[0]
2570
g += plot(m*x + b, (click[0]-1,click[0]+1), color='red', zorder=10)
2571
def h(p):
2572
f0.mousemove = p
2573
def c(p):
2574
f0(click=p)
2575
show(g, events={'click':c, 'mousemove':h}, svg=True, gridlines='major', ymin=ymin, ymax=ymax)
2576
"""
2577
# svg=True, d3=True,
2578
svg = kwds.get('svg',True)
2579
d3 = kwds.get('d3',True)
2580
display = kwds.get('display', True)
2581
for t in ['svg', 'd3', 'display']:
2582
if t in kwds:
2583
del kwds[t]
2584
import graphics
2585
def show0(obj, combine_all=False):
2586
# Either show the object and return None or
2587
# return a string of html to represent obj.
2588
if isinstance(obj, (Graphics, GraphicsArray, matplotlib.figure.Figure, matplotlib.axes.Axes, matplotlib.image.AxesImage)):
2589
show_2d_plot_using_matplotlib(obj, svg=svg, **kwds)
2590
elif isinstance(obj, Animation):
2591
show_animation(obj, **kwds)
2592
elif isinstance(obj, Graphics3d):
2593
2594
# _extra_kwds processing follows the example of
2595
# src/smc_sagews/smc_sagews/graphics.py:show_3d_plot_using_threejs()
2596
extra_kwds = {} if obj._extra_kwds is None else obj._extra_kwds
2597
for k in ['spin', 'renderer', 'viewer', 'frame', 'height', 'width', 'background', 'foreground', 'aspect_ratio']:
2598
if k in extra_kwds and k not in kwds:
2599
kwds[k] = obj._extra_kwds[k]
2600
2601
if kwds.get('viewer') == 'tachyon':
2602
show_3d_plot_using_tachyon(obj, **kwds)
2603
else:
2604
salvus.threed(obj, **kwds)
2605
# graphics.show_3d_plot_using_threejs(obj, **kwds)
2606
elif isinstance(obj, Tachyon):
2607
show_3d_plot_using_tachyon(obj, **kwds)
2608
elif isinstance(obj, (sage.graphs.graph.Graph, sage.graphs.digraph.DiGraph)):
2609
if d3:
2610
show_graph_using_d3(obj, **kwds)
2611
else:
2612
show(obj.plot(), **kwds)
2613
elif isinstance(obj, str):
2614
return obj
2615
elif isinstance(obj, (list, tuple)):
2616
v = []
2617
for a in obj:
2618
b = show0(a)
2619
if b is not None:
2620
v.append(b)
2621
if combine_all:
2622
return ' '.join(v)
2623
s = ', '.join(v)
2624
if isinstance(obj, list):
2625
return '[%s]'%s
2626
else:
2627
return '(%s)'%s
2628
elif is_dataframe(obj):
2629
html(obj.to_html(), hide=False)
2630
else:
2631
s = str(sage.misc.latex.latex(obj))
2632
if r'\text{\texttt' in s and 'tikzpicture' not in s:
2633
# In this case the mathjax latex mess is so bad, it is better to just print and give up!
2634
print(obj)
2635
return
2636
# Add anything here that Sage produces and mathjax can't handle, and
2637
# which people complain about... (obviously, I wish there were a way to
2638
# know -- e.g., if Sage had a way to tell whether latex it produces
2639
# will work with mathjax or not).
2640
if '\\begin{tikzpicture}' in s or '\\raisebox' in s:
2641
# special case -- mathjax has no support for tikz or \raisebox so we just immediately display it (as a png); this is
2642
# better than nothing.
2643
sage.misc.latex.latex.eval(s)
2644
return ''
2645
elif r'\begin{tabular}' in s:
2646
# tabular is an environment for text, not formular.
2647
# Sage's `tabular` should actually use \array!
2648
sage.misc.latex.latex.eval(s)
2649
return ''
2650
# default
2651
elif display:
2652
return "$\\displaystyle %s$"%s
2653
else:
2654
return "$%s$"%s
2655
sys.stdout.flush()
2656
sys.stderr.flush()
2657
s = show0(objs, combine_all=True)
2658
if s is not None:
2659
if len(s) > 0:
2660
if display:
2661
salvus.html("<div align='center'>%s</div>"%cgi.escape(s))
2662
else:
2663
salvus.html("<div>%s</div>"%cgi.escape(s))
2664
sys.stdout.flush()
2665
sys.stderr.flush()
2666
2667
# Make it so plots plot themselves correctly when they call their repr.
2668
Graphics.show = show
2669
GraphicsArray.show = show
2670
Animation.show = show
2671
2672
# Very "evil" abuse of the display manager, so sphere().show() works:
2673
try:
2674
from sage.repl.rich_output import get_display_manager
2675
get_display_manager().display_immediately = show
2676
except:
2677
# so doesn't crash on older versions of Sage.
2678
pass
2679
2680
###################################################
2681
# %auto -- automatically evaluate a cell on load
2682
###################################################
2683
def auto(s):
2684
"""
2685
The %auto decorator sets a cell so that it will be automatically
2686
executed when the Sage process first starts. Make it the first
2687
line of a cell.
2688
2689
Thus %auto allows you to initialize functions, variables, interacts,
2690
etc., e.g., when loading a worksheet.
2691
"""
2692
return s # the do-nothing block decorator.
2693
2694
def hide(component='input'):
2695
"""
2696
Hide a component of a cell. By default, hide hides the the code
2697
editor part of the cell, but you can hide other parts by passing
2698
in an optional argument:
2699
2700
'input', 'output'
2701
2702
Use the cell.show(...) function to reveal a cell component.
2703
"""
2704
if component not in ['input', 'output']:
2705
# Allow %hide to work, for compatability with sagenb.
2706
hide('input')
2707
return component
2708
cell.hide(component)
2709
2710
def hideall(code=None):
2711
cell.hideall()
2712
if code is not None: # for backwards compat with sagenb
2713
return code
2714
2715
2716
##########################################################
2717
# A "%exercise" cell mode -- a first step toward
2718
# automated homework.
2719
##########################################################
2720
class Exercise:
2721
def __init__(self, question, answer, check=None, hints=None):
2722
import sage.all
2723
from sage.structure.element import is_Matrix
2724
if not (isinstance(answer, (tuple, list)) and len(answer) == 2):
2725
if is_Matrix(answer):
2726
default = sage.all.parent(answer)(0)
2727
else:
2728
default = ''
2729
answer = [answer, default]
2730
2731
if check is None:
2732
R = sage.all.parent(answer[0])
2733
def check(attempt):
2734
return R(attempt) == answer[0]
2735
2736
if hints is None:
2737
hints = ['','','',"The answer is %s."%answer[0]]
2738
2739
self._question = question
2740
self._answer = answer
2741
self._check = check
2742
self._hints = hints
2743
2744
def _check_attempt(self, attempt, interact):
2745
from sage.misc.all import walltime
2746
response = "<div class='well'>"
2747
try:
2748
r = self._check(attempt)
2749
if isinstance(r, tuple) and len(r)==2:
2750
correct = r[0]
2751
comment = r[1]
2752
else:
2753
correct = bool(r)
2754
comment = ''
2755
except TypeError as msg:
2756
response += "<h3 style='color:darkgreen'>Huh? -- %s (attempt=%s)</h3>"%(msg, attempt)
2757
else:
2758
if correct:
2759
response += "<h1 style='color:blue'>RIGHT!</h1>"
2760
if self._start_time:
2761
response += "<h2 class='lighten'>Time: %.1f seconds</h2>"%(walltime()-self._start_time,)
2762
if self._number_of_attempts == 1:
2763
response += "<h3 class='lighten'>You got it first try!</h3>"
2764
else:
2765
response += "<h3 class='lighten'>It took you %s attempts.</h3>"%(self._number_of_attempts,)
2766
else:
2767
response += "<h3 style='color:darkgreen'>Not correct yet...</h3>"
2768
if self._number_of_attempts == 1:
2769
response += "<h4 style='lighten'>(first attempt)</h4>"
2770
else:
2771
response += "<h4 style='lighten'>(%s attempts)</h4>"%self._number_of_attempts
2772
2773
if self._number_of_attempts > len(self._hints):
2774
hint = self._hints[-1]
2775
else:
2776
hint = self._hints[self._number_of_attempts-1]
2777
if hint:
2778
response += "<span class='lighten'>(HINT: %s)</span>"%(hint,)
2779
if comment:
2780
response += '<h4>%s</h4>'%comment
2781
2782
response += "</div>"
2783
2784
interact.feedback = text_control(response,label='')
2785
2786
return correct
2787
2788
def ask(self, cb):
2789
from sage.misc.all import walltime
2790
self._start_time = walltime()
2791
self._number_of_attempts = 0
2792
attempts = []
2793
@interact(layout=[[('question',12)],[('attempt',12)], [('feedback',12)]])
2794
def f(question = ("<b>Question:</b>", text_control(self._question)),
2795
attempt = ('<b>Answer:</b>',self._answer[1])):
2796
if 'attempt' in interact.changed() and attempt != '':
2797
attempts.append(attempt)
2798
if self._start_time == 0:
2799
self._start_time = walltime()
2800
self._number_of_attempts += 1
2801
if self._check_attempt(attempt, interact):
2802
cb({'attempts':attempts, 'time':walltime()-self._start_time})
2803
2804
def exercise(code):
2805
r"""
2806
Use the %exercise cell decorator to create interactive exercise
2807
sets. Put %exercise at the top of the cell, then write Sage code
2808
in the cell that defines the following (all are optional):
2809
2810
- a ``question`` variable, as an HTML string with math in dollar
2811
signs
2812
2813
- an ``answer`` variable, which can be any object, or a pair
2814
(correct_value, interact control) -- see the docstring for
2815
interact for controls.
2816
2817
- an optional callable ``check(answer)`` that returns a boolean or
2818
a 2-tuple
2819
2820
(True or False, message),
2821
2822
where the first argument is True if the answer is correct, and
2823
the optional second argument is a message that should be
2824
displayed in response to the given answer. NOTE: Often the
2825
input "answer" will be a string, so you may have to use Integer,
2826
RealNumber, or sage_eval to evaluate it, depending
2827
on what you want to allow the user to do.
2828
2829
- hints -- optional list of strings to display in sequence each
2830
time the user enters a wrong answer. The last string is
2831
displayed repeatedly. If hints is omitted, the correct answer
2832
is displayed after three attempts.
2833
2834
NOTE: The code that defines the exercise is executed so that it
2835
does not impact (and is not impacted by) the global scope of your
2836
variables elsewhere in your session. Thus you can have many
2837
%exercise cells in a single worksheet with no interference between
2838
them.
2839
2840
The following examples further illustrate how %exercise works.
2841
2842
An exercise to test your ability to sum the first $n$ integers::
2843
2844
%exercise
2845
title = "Sum the first n integers, like Gauss did."
2846
n = randint(3, 100)
2847
question = "What is the sum $1 + 2 + \\cdots + %s$ of the first %s positive integers?"%(n,n)
2848
answer = n*(n+1)//2
2849
2850
Transpose a matrix::
2851
2852
%exercise
2853
title = r"Transpose a $2 \times 2$ Matrix"
2854
A = random_matrix(ZZ,2)
2855
question = "What is the transpose of $%s?$"%latex(A)
2856
answer = A.transpose()
2857
2858
Add together a few numbers::
2859
2860
%exercise
2861
k = randint(2,5)
2862
title = "Add %s numbers"%k
2863
v = [randint(1,10) for _ in range(k)]
2864
question = "What is the sum $%s$?"%(' + '.join([str(x) for x in v]))
2865
answer = sum(v)
2866
2867
The trace of a matrix::
2868
2869
%exercise
2870
title = "Compute the trace of a matrix."
2871
A = random_matrix(ZZ, 3, x=-5, y = 5)^2
2872
question = "What is the trace of $$%s?$$"%latex(A)
2873
answer = A.trace()
2874
2875
Some basic arithmetic with hints and dynamic feedback::
2876
2877
%exercise
2878
k = randint(2,5)
2879
title = "Add %s numbers"%k
2880
v = [randint(1,10) for _ in range(k)]
2881
question = "What is the sum $%s$?"%(' + '.join([str(x) for x in v]))
2882
answer = sum(v)
2883
hints = ['This is basic arithmetic.', 'The sum is near %s.'%(answer+randint(1,5)), "The answer is %s."%answer]
2884
def check(attempt):
2885
c = Integer(attempt) - answer
2886
if c == 0:
2887
return True
2888
if abs(c) >= 10:
2889
return False, "Gees -- not even close!"
2890
if c < 0:
2891
return False, "too low"
2892
if c > 0:
2893
return False, "too high"
2894
"""
2895
f = closure(code)
2896
def g():
2897
x = f()
2898
return x.get('title',''), x.get('question', ''), x.get('answer',''), x.get('check',None), x.get('hints',None)
2899
2900
title, question, answer, check, hints = g()
2901
obj = {}
2902
obj['E'] = Exercise(question, answer, check, hints)
2903
obj['title'] = title
2904
def title_control(t):
2905
return text_control('<h3 class="lighten">%s</h3>'%t)
2906
2907
the_times = []
2908
@interact(layout=[[('go',1), ('title',11,'')],[('')], [('times',12, "<b>Times:</b>")]], flicker=True)
2909
def h(go = button("&nbsp;"*5 + "Go" + "&nbsp;"*7, label='', icon='fa-refresh', classes="btn-large btn-success"),
2910
title = title_control(title),
2911
times = text_control('')):
2912
c = interact.changed()
2913
if 'go' in c or 'another' in c:
2914
interact.title = title_control(obj['title'])
2915
def cb(obj):
2916
the_times.append("%.1f"%obj['time'])
2917
h.times = ', '.join(the_times)
2918
2919
obj['E'].ask(cb)
2920
2921
title, question, answer, check, hints = g() # get ready for next time.
2922
obj['title'] = title
2923
obj['E'] = Exercise(question, answer, check, hints)
2924
2925
def closure(code):
2926
"""
2927
Wrap the given code block (a string) in a closure, i.e., a
2928
function with an obfuscated random name.
2929
2930
When called, the function returns locals().
2931
"""
2932
import uuid
2933
# TODO: strip string literals first
2934
code = ' ' + ('\n '.join(code.splitlines()))
2935
fname = "__" + str(uuid.uuid4()).replace('-','_')
2936
closure = "def %s():\n%s\n return locals()"%(fname, code)
2937
class Closure:
2938
def __call__(self):
2939
return self._f()
2940
c = Closure()
2941
salvus.execute(closure)
2942
c._f = salvus.namespace[fname]
2943
del salvus.namespace[fname]
2944
return c
2945
2946
2947
#########################################
2948
# Dynamic variables (linked to controls)
2949
#########################################
2950
2951
def _dynamic(var, control=None):
2952
if control is None:
2953
control = salvus.namespace.get(var,'')
2954
2955
@interact(layout=[[(var,12)]], output=False)
2956
def f(x=(var,control)):
2957
salvus.namespace.set(var, x, do_not_trigger=[var])
2958
2959
def g(y):
2960
f.x = y
2961
salvus.namespace.on('change', var, g)
2962
2963
if var in salvus.namespace:
2964
x = salvus.namespace[var]
2965
2966
def dynamic(*args, **kwds):
2967
"""
2968
Make variables in the global namespace dynamically linked to a control from the
2969
interact label (see the documentation for interact).
2970
2971
EXAMPLES:
2972
2973
Make a control linked to a variable that doesn't yet exist::
2974
2975
dynamic('xyz')
2976
2977
Make a slider and a selector, linked to t and x::
2978
2979
dynamic(t=(1..10), x=[1,2,3,4])
2980
t = 5 # this changes the control
2981
"""
2982
for var in args:
2983
if not isinstance(var, str):
2984
i = id(var)
2985
for k,v in salvus.namespace.iteritems():
2986
if id(v) == i:
2987
_dynamic(k)
2988
return
2989
else:
2990
_dynamic(var)
2991
2992
for var, control in kwds.iteritems():
2993
_dynamic(var, control)
2994
2995
2996
import sage.all
2997
2998
def var0(*args, **kwds):
2999
if len(args)==1:
3000
name = args[0]
3001
else:
3002
name = args
3003
G = salvus.namespace
3004
v = sage.all.SR.var(name, **kwds)
3005
if isinstance(v, tuple):
3006
for x in v:
3007
G[repr(x)] = x
3008
else:
3009
G[repr(v)] = v
3010
return v
3011
3012
def var(*args, **kwds):
3013
"""
3014
Create symbolic variables and inject them into the global namespace.
3015
3016
NOTE: In CoCalc, you can use var as a line decorator::
3017
3018
%var x
3019
%var a,b,theta # separate with commas
3020
%var x y z t # separate with spaces
3021
3022
Use latex_name to customizing how the variables is typeset:
3023
3024
var1 = var('var1', latex_name=r'\sigma^2_1')
3025
show(e^(var1**2))
3026
3027
Multicolored variables made using the %var line decorator:
3028
3029
%var(latex_name=r"\color{green}{\theta}") theta
3030
%var(latex_name=r"\color{red}{S_{u,i}}") sui
3031
show(expand((sui + x^3 + theta)^2))
3032
3033
3034
3035
Here is the docstring for var in Sage:
3036
3037
"""
3038
if 'latex_name' in kwds:
3039
# wrap with braces -- sage should probably do this, but whatever.
3040
kwds['latex_name'] = '{%s}'%kwds['latex_name']
3041
if len(args) > 0:
3042
return var0(*args, **kwds)
3043
else:
3044
def f(s):
3045
return var0(s, *args, **kwds)
3046
return f
3047
3048
var.__doc__ += sage.all.var.__doc__
3049
3050
3051
3052
#############################################
3053
# Variable reset -- we have to rewrite
3054
# this because of all the monkey patching
3055
# that we do.
3056
#############################################
3057
3058
import sage.misc.reset
3059
3060
def reset(vars=None, attached=False):
3061
"""
3062
If vars is specified, just restore the value of vars and leave
3063
all other variables alone. In CoCalc, you can also use
3064
reset as a line decorator::
3065
3066
%reset x, pi, sin # comma-separated
3067
%reset x pi sin # commas are optional
3068
3069
If vars is not given, delete all user-defined variables, reset
3070
all global variables back to their default states, and reset
3071
all interfaces to other computer algebra systems.
3072
3073
Original reset docstring::
3074
3075
"""
3076
if vars is not None:
3077
restore(vars)
3078
return
3079
G = salvus.namespace
3080
T = type(sys) # module type
3081
for k in G.keys():
3082
if k[0] != '_' and type(k) != T:
3083
try:
3084
del G[k]
3085
except KeyError:
3086
pass
3087
restore()
3088
from sage.symbolic.assumptions import forget; forget()
3089
sage.misc.reset.reset_interfaces()
3090
if attached:
3091
sage.misc.reset.reset_attached()
3092
# reset() adds 'pretty_print' and 'view' to show_identifiers()
3093
# user can shadow these and they will appear in show_identifiers()
3094
# 'sage_salvus' is added when the following line runs; user may not shadow it
3095
exec('sage.misc.session.state_at_init = dict(globals())',salvus.namespace)
3096
3097
reset.__doc__ += sage.misc.reset.reset.__doc__
3098
3099
def restore(vars=None):
3100
""
3101
if isinstance(vars, unicode):
3102
vars = str(vars) # sage.misc.reset is unicode ignorant
3103
if ',' in vars: # sage.misc.reset is stupid about commas and space -- TODO: make a patch to sage
3104
vars = [v.strip() for v in vars.split(',')]
3105
import sage.calculus.calculus
3106
sage.misc.reset._restore(salvus.namespace, default_namespace, vars)
3107
sage.misc.reset._restore(sage.calculus.calculus.syms_cur, sage.calculus.calculus.syms_default, vars)
3108
3109
restore.__doc__ += sage.misc.reset.restore.__doc__
3110
3111
# NOTE: this is not used anymore
3112
def md2html(s):
3113
from markdown2Mathjax import sanitizeInput, reconstructMath
3114
from markdown2 import markdown
3115
3116
delims = [('\\(','\\)'), ('$$','$$'), ('\\[','\\]'),
3117
('\\begin{equation}', '\\end{equation}'), ('\\begin{equation*}', '\\end{equation*}'),
3118
('\\begin{align}', '\\end{align}'), ('\\begin{align*}', '\\end{align*}'),
3119
('\\begin{eqnarray}', '\\end{eqnarray}'), ('\\begin{eqnarray*}', '\\end{eqnarray*}'),
3120
('\\begin{math}', '\\end{math}'),
3121
('\\begin{displaymath}', '\\end{displaymath}')
3122
]
3123
3124
tmp = [((s,None),None)]
3125
for d in delims:
3126
tmp.append((sanitizeInput(tmp[-1][0][0], equation_delims=d), d))
3127
3128
extras = ['code-friendly', 'footnotes', 'smarty-pants', 'wiki-tables']
3129
markedDownText = markdown(tmp[-1][0][0], extras=extras)
3130
3131
while len(tmp) > 1:
3132
markedDownText = reconstructMath(markedDownText, tmp[-1][0][1], equation_delims=tmp[-1][1])
3133
del tmp[-1]
3134
3135
return markedDownText
3136
3137
# NOTE: this is not used anymore
3138
class Markdown(object):
3139
r"""
3140
Cell mode that renders everything after %md as markdown.
3141
3142
EXAMPLES::
3143
3144
---
3145
%md
3146
# A Title
3147
3148
## A subheading
3149
3150
---
3151
%md(hide=True)
3152
# A title
3153
3154
- a list
3155
3156
---
3157
md("# A title")
3158
3159
3160
---
3161
%md `some code`
3162
3163
3164
This uses the Python markdown2 library with the following
3165
extras enabled:
3166
3167
'code-friendly', 'footnotes',
3168
'smarty-pants', 'wiki-tables'
3169
3170
See https://github.com/trentm/python-markdown2/wiki/Extras
3171
We also use markdown2Mathjax so that LaTeX will be properly
3172
typeset if it is wrapped in $'s and $$'s, \(, \), \[, \],
3173
\begin{equation}, \end{equation}, \begin{align}, \end{align}.,
3174
"""
3175
def __init__(self, hide=False):
3176
self._hide = hide
3177
3178
def __call__(self, *args, **kwds):
3179
if len(kwds) > 0 and len(args) == 0:
3180
return Markdown(**kwds)
3181
if len(args) > 0:
3182
self._render(args[0], **kwds)
3183
3184
def _render(self, s, hide=None):
3185
if hide is None:
3186
hide = self._hide
3187
html(md2html(s),hide=hide)
3188
3189
# not used
3190
#md = Markdown()
3191
3192
# Instead... of the above server-side markdown, we use this client-side markdown.
3193
3194
class Marked(object):
3195
r"""
3196
Cell mode that renders everything after %md as Github flavored
3197
markdown [1] with mathjax and hides the input by default.
3198
3199
[1] https://help.github.com/articles/github-flavored-markdown
3200
3201
The rendering is done client-side using marked and mathjax.
3202
3203
EXAMPLES::
3204
3205
---
3206
%md
3207
# A Title
3208
3209
## A subheading
3210
3211
---
3212
%md(hide=False)
3213
# A title
3214
3215
- a list
3216
3217
---
3218
md("# A title", hide=False)
3219
3220
3221
---
3222
%md(hide=False) `some code`
3223
3224
"""
3225
def __init__(self, hide=False):
3226
self._hide = hide
3227
3228
def __call__(self, *args, **kwds):
3229
if len(kwds) > 0 and len(args) == 0:
3230
return Marked(**kwds)
3231
if len(args) > 0:
3232
self._render(args[0], **kwds)
3233
3234
def _render(self, s, hide=None):
3235
if hide is None:
3236
hide = self._hide
3237
if hide:
3238
salvus.hide('input')
3239
salvus.md(s)
3240
3241
md = Marked()
3242
3243
#####
3244
## Raw Input
3245
# - this is the Python 2.x interpretation. In Python 3.x there is no raw_input,
3246
# and raw_input is renamed input (to cause more confusion).
3247
#####
3248
def raw_input(prompt='', default='', placeholder='', input_width=None, label_width=None, type=None):
3249
"""
3250
Read a string from the user in the worksheet interface to Sage.
3251
3252
INPUTS:
3253
3254
- prompt -- (default: '') a label to the left of the input
3255
- default -- (default: '') default value to put in input box
3256
- placeholder -- (default: '') default placeholder to put in grey when input box empty
3257
- input_width -- (default: None) css that gives the width of the input box
3258
- label_width -- (default: None) css that gives the width of the label
3259
- type -- (default: None) if not given, returns a unicode string representing the exact user input.
3260
Other options include:
3261
- type='sage' -- will evaluate it to a sage expression in the global scope.
3262
- type=anything that can be called, e.g., type=int, type=float.
3263
3264
OUTPUT:
3265
3266
- By default, returns a **unicode** string (not a normal Python str). However, can be customized
3267
by changing the type.
3268
3269
EXAMPLE::
3270
3271
print(raw_input("What is your full name?", default="Sage Math", input_width="20ex", label_width="25ex"))
3272
3273
"""
3274
return salvus.raw_input(prompt=prompt, default=default, placeholder=placeholder, input_width=input_width, label_width=label_width, type=type)
3275
3276
def input(*args, **kwds):
3277
"""
3278
Read a string from the user in the worksheet interface to Sage and return evaluated object.
3279
3280
Type raw_input? for more help; this function is the same as raw_input, except with type='sage'.
3281
3282
EXAMPLE::
3283
3284
print(type(input("What is your age", default=18, input_width="20ex", label_width="25ex")))
3285
3286
"""
3287
kwds['type'] = 'sage'
3288
return raw_input(*args, **kwds)
3289
3290
#####
3291
## Clear
3292
def clear():
3293
"""
3294
Clear the output of the current cell. You can use this to
3295
dynamically animate the output of a cell using a for loop.
3296
3297
SEE ALSO: delete_last_output
3298
"""
3299
salvus.clear()
3300
3301
def delete_last_output():
3302
"""
3303
Delete the last output message.
3304
3305
SEE ALSO: clear
3306
"""
3307
salvus.delete_last_output()
3308
3309
#####
3310
# Generic Pandoc cell decorator
3311
3312
def pandoc(fmt, doc=None, hide=True):
3313
"""
3314
INPUT:
3315
3316
- fmt -- one of 'docbook', 'haddock', 'html', 'json', 'latex', 'markdown', 'markdown_github',
3317
'markdown_mmd', 'markdown_phpextra', 'markdown_strict', 'mediawiki',
3318
'native', 'opml', 'rst', 'textile'
3319
3320
- doc -- a string in the given format
3321
3322
OUTPUT:
3323
3324
- Called directly, you get the HTML rendered version of doc as a string.
3325
3326
- If you use this as a cell decorator, it displays the HTML output, e.g.,
3327
3328
%pandoc('mediawiki')
3329
* ''Unordered lists'' are easy to do:
3330
** Start every line with a star.
3331
*** More stars indicate a deeper level.
3332
3333
"""
3334
if doc is None:
3335
return lambda x : html(pandoc(fmt, x), hide=hide) if x is not None else ''
3336
import subprocess
3337
p = subprocess.Popen(['pandoc', '-f', fmt, '--mathjax'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
3338
if not isinstance(doc, unicode):
3339
doc = unicode(doc, 'utf8')
3340
p.stdin.write(doc.encode('UTF-8'))
3341
p.stdin.close()
3342
err = p.stderr.read()
3343
if err:
3344
raise RuntimeError(err)
3345
return p.stdout.read()
3346
3347
3348
def wiki(doc=None, hide=True):
3349
"""
3350
Mediawiki markup cell decorator. E.g.,
3351
3352
EXAMPLE::
3353
3354
%wiki(hide=False)
3355
* ''Unordered lists'' and math like $x^3 - y^2$ are both easy
3356
** Start every line with a star.
3357
*** More stars indicate a deeper level. """
3358
if doc is None:
3359
return lambda doc: wiki(doc=doc, hide=hide) if doc else ''
3360
html(pandoc('mediawiki', doc=doc), hide=hide)
3361
3362
3363
mediawiki = wiki
3364
3365
######
3366
3367
def load_html_resource(filename):
3368
fl = filename.lower()
3369
if fl.startswith('http://') or fl.startswith('https://'):
3370
# remote url
3371
url = fl
3372
else:
3373
# local file
3374
url = salvus.file(filename, show=False)
3375
ext = os.path.splitext(filename)[1][1:].lower()
3376
if ext == "css":
3377
salvus.javascript('''$.get("%s", function(css) { $('<style type=text/css></style>').html(css).appendTo("body")});'''%url)
3378
elif ext == "html":
3379
salvus.javascript('element.append($("<div>").load("%s"))'%url)
3380
elif ext == "coffee":
3381
salvus.coffeescript('$.ajax({url:"%s"}).done (data) ->\n eval(CoffeeScript.compile(data))'%url)
3382
elif ext == "js":
3383
salvus.html('<script src="%s"></script>'%url)
3384
3385
def attach(*args):
3386
r"""
3387
Load file(s) into the Sage worksheet process and add to list of attached files.
3388
All attached files that have changed since they were last loaded are reloaded
3389
the next time a worksheet cell is executed.
3390
3391
INPUT:
3392
3393
- ``files`` - list of strings, filenames to attach
3394
3395
.. SEEALSO::
3396
3397
:meth:`sage.repl.attach.attach` docstring has details on how attached files
3398
are handled
3399
"""
3400
# can't (yet) pass "attach = True" to load(), so do this
3401
3402
if len(args) == 1:
3403
if isinstance(args[0], (unicode,str)):
3404
args = tuple(args[0].replace(',',' ').split())
3405
if isinstance(args[0], (list, tuple)):
3406
args = args[0]
3407
try:
3408
from sage.repl.attach import load_attach_path
3409
except ImportError:
3410
raise NotImplementedError("sage_salvus: attach not available")
3411
3412
for fname in args:
3413
for path in load_attach_path():
3414
fpath = os.path.join(path, fname)
3415
fpath = os.path.expanduser(fpath)
3416
if os.path.isfile(fpath):
3417
load(fname)
3418
sage.repl.attach.add_attached_file(fpath)
3419
break
3420
else:
3421
raise IOError('did not find file %r to attach' % fname)
3422
3423
3424
# Monkey-patched the load command
3425
def load(*args, **kwds):
3426
"""
3427
Load Sage object from the file with name filename, which will have
3428
an .sobj extension added if it doesn't have one. Or, if the input
3429
is a filename ending in .py, .pyx, or .sage, load that file into
3430
the current running session. Loaded files are not loaded into
3431
their own namespace, i.e., this is much more like Python's
3432
"execfile" than Python's "import".
3433
3434
You may also load an sobj or execute a code file available on the web
3435
by specifying the full URL to the file. (Set ``verbose = False`` to
3436
supress the download progress indicator.)
3437
3438
INPUT:
3439
3440
- args -- any number of filename strings with any of the following extensions:
3441
3442
.sobj, .sage, .py, .pyx, .html, .css, .js, .coffee, .pdf
3443
3444
- ``verbose`` -- (default: True) load file over the network.
3445
3446
If you load any of the web types (.html, .css, .js, .coffee), they are loaded
3447
into the web browser DOM (or Javascript session), not the Python process.
3448
3449
If you load a pdf, it is displayed in the output of the worksheet. The extra
3450
options are passed to smc.pdf -- see the docstring for that.
3451
3452
In CoCalc you may also use load as a decorator, with exactly one filename as input::
3453
3454
%load foo.sage
3455
3456
This loads a single file whose name has a space in it::
3457
3458
%load a b.sage
3459
3460
The following are all valid ways to use load::
3461
3462
%load a.html
3463
%load a.css
3464
%load a.js
3465
%load a.coffee
3466
%load a.css
3467
load('a.css', 'a.js', 'a.coffee', 'a.html')
3468
load(['a.css', 'a.js', 'a.coffee', 'a.html'])
3469
3470
ALIAS: %runfile is the same as %load, for compatibility with IPython.
3471
"""
3472
if len(args) == 1:
3473
if isinstance(args[0], (unicode, str)):
3474
args = (args[0].strip(), )
3475
if isinstance(args[0], (list, tuple)):
3476
args = args[0]
3477
3478
if len(args) == 0 and len(kwds) == 1:
3479
# This supports
3480
# %load(verbose=False) a.sage
3481
# which doesn't really matter right now, since there is a bug in Sage's own
3482
# load command, where it isn't verbose for network code, but is for objects.
3483
def f(*args):
3484
return load(*args, **kwds)
3485
return f
3486
3487
t = '__tmp__'; i=0
3488
while t+str(i) in salvus.namespace:
3489
i += 1
3490
t += str(i)
3491
3492
# First handle HTML related args -- these are all very oriented toward cloud.sagemath worksheets
3493
html_extensions = set(['js','css','coffee','html'])
3494
other_args = []
3495
for arg in args:
3496
i = arg.rfind('.')
3497
if i != -1 and arg[i+1:].lower() in html_extensions:
3498
load_html_resource(arg)
3499
elif i != -1 and arg[i+1:].lower() == 'pdf':
3500
show_pdf(arg, **kwds)
3501
else:
3502
other_args.append(arg)
3503
3504
# pdf?
3505
for arg in args:
3506
i = arg.find('.')
3507
3508
# now handle remaining non-web arguments.
3509
if len(other_args) > 0:
3510
try:
3511
exec 'salvus.namespace["%s"] = sage.structure.sage_object.load(*__args, **__kwds)'%t in salvus.namespace, {'__args':other_args, '__kwds':kwds}
3512
return salvus.namespace[t]
3513
finally:
3514
try:
3515
del salvus.namespace[t]
3516
except: pass
3517
3518
# add alias, due to IPython.
3519
runfile = load
3520
3521
## Make it so pylab (matplotlib) figures display, at least using pylab.show
3522
import pylab
3523
def _show_pylab(svg=True):
3524
"""
3525
Show a Pylab plot in a Sage Worksheet.
3526
3527
INPUTS:
3528
3529
- svg -- boolean (default: True); if True use an svg; otherwise, use a png.
3530
"""
3531
try:
3532
ext = '.svg' if svg else '.png'
3533
filename = uuid() + ext
3534
pylab.savefig(filename)
3535
salvus.file(filename)
3536
finally:
3537
try:
3538
os.unlink(filename)
3539
except:
3540
pass
3541
3542
pylab.show = _show_pylab
3543
matplotlib.figure.Figure.show = show
3544
3545
import matplotlib.pyplot
3546
def _show_pyplot(svg=True):
3547
"""
3548
Show a Pylab plot in a Sage Worksheet.
3549
3550
INPUTS:
3551
3552
- svg -- boolean (default: True); if True use an svg; otherwise, use a png.
3553
"""
3554
try:
3555
ext = '.svg' if svg else '.png'
3556
filename = uuid() + ext
3557
matplotlib.pyplot.savefig(filename)
3558
salvus.file(filename)
3559
finally:
3560
try:
3561
os.unlink(filename)
3562
except:
3563
pass
3564
matplotlib.pyplot.show = _show_pyplot
3565
3566
3567
## Our own displayhook
3568
3569
_system_sys_displayhook = sys.displayhook
3570
3571
def displayhook(obj):
3572
if isinstance(obj, (Graphics3d, Graphics, GraphicsArray, matplotlib.figure.Figure, matplotlib.axes.Axes, matplotlib.image.AxesImage, Animation, Tachyon)):
3573
show(obj)
3574
else:
3575
_system_sys_displayhook(obj)
3576
3577
sys.displayhook = displayhook
3578
import sage.misc.latex, types
3579
# We make this a list so that users can append to it easily.
3580
TYPESET_MODE_EXCLUDES = [sage.misc.latex.LatexExpr, types.NoneType,
3581
type, sage.plot.plot3d.base.Graphics3d,
3582
sage.plot.graphics.Graphics,
3583
sage.plot.graphics.GraphicsArray]
3584
3585
def typeset_mode(on=True, display=True, **args):
3586
"""
3587
Turn typeset mode on or off. When on, each output is typeset using LaTeX.
3588
3589
EXAMPLES::
3590
3591
typeset_mode() # turns typesetting on
3592
3593
typeset_mode(False) # turn typesetting off
3594
3595
typeset_mode(True, display=False) # typesetting mode on, but do not make output big and centered
3596
3597
"""
3598
if isinstance(on, (str, unicode)): # e.g., %typeset_mode False
3599
on = sage_eval(on, {'false':False, 'true':True})
3600
if on:
3601
def f(obj):
3602
if isinstance(obj, tuple(TYPESET_MODE_EXCLUDES)):
3603
displayhook(obj)
3604
else:
3605
show(obj, display=display)
3606
sys.displayhook = f
3607
else:
3608
sys.displayhook = displayhook
3609
3610
def default_mode(mode):
3611
"""
3612
Set the default mode for cell evaluation. This is equivalent
3613
to putting %mode at the top of any cell that does not start
3614
with %. Use default_mode() to return the current mode.
3615
Use default_mode("") to have no default mode.
3616
3617
EXAMPLES::
3618
3619
Make Pari/GP the default mode:
3620
3621
default_mode("gp")
3622
default_mode() # outputs "gp"
3623
3624
Then switch back to Sage::
3625
3626
default_mode("") # or default_mode("sage")
3627
3628
You can also use default_mode as a line decorator::
3629
3630
%default_mode gp # equivalent to default_mode("gp")
3631
"""
3632
return salvus.default_mode(mode)
3633
3634
3635
3636
3637
3638
3639
#######################################################
3640
# Monkey patching and deprecation --
3641
#######################################################
3642
3643
# Monkey patch around a bug in Python's findsource that breaks deprecation in cloud worksheets.
3644
# This won't matter if we switch to not using exec, since then there will be a file behind
3645
# each block of code. However, for now we have to do this.
3646
import inspect
3647
_findsource = inspect.findsource
3648
def findsource(object):
3649
try: return _findsource(object)
3650
except: raise IOError('source code not available') # as *claimed* by the Python docs!
3651
inspect.findsource = findsource
3652
3653
3654
3655
#######################################################
3656
# Viewing pdf's
3657
#######################################################
3658
3659
def show_pdf(filename, viewer="object", width=1000, height=600, scale=1.6):
3660
"""
3661
Display a PDF file from the filesystem in an output cell of a worksheet.
3662
3663
It uses the HTML object tag, which uses either the browser plugin,
3664
or provides a download link in case the browser can't display pdf's.
3665
3666
INPUT:
3667
3668
- filename
3669
- width -- (default: 1000) -- pixel width of viewer
3670
- height -- (default: 600) -- pixel height of viewer
3671
"""
3672
url = salvus.file(filename, show=False)
3673
s = '''<object data="%s" type="application/pdf" width="%s" height="%s">
3674
<p>Your browser doesn't support embedded PDF's, but you can <a href="%s">download %s</a></p>
3675
</object>'''%(url, width, height, url, filename)
3676
salvus.html(s)
3677
3678
3679
########################################################
3680
# WebRTC Support
3681
########################################################
3682
def sage_chat(chatroom=None, height="258px"):
3683
if chatroom is None:
3684
from random import randint
3685
chatroom = randint(0,1e24)
3686
html("""
3687
<iframe src="/static/webrtc/group_chat_cell.html?%s" height="%s" width="100%%"></iframe>
3688
"""%(chatroom, height), hide=False)
3689
3690
3691
########################################################
3692
# Documentation of modes
3693
########################################################
3694
def modes():
3695
"""
3696
To use a mode command, either type
3697
3698
%command <a line of code>
3699
3700
or
3701
3702
%command
3703
[rest of cell]
3704
3705
Create your own mode command by defining a function that takes
3706
a string as input and outputs a string. (Yes, it is that simple.)
3707
"""
3708
import re
3709
mode_cmds = set()
3710
for s in open(os.path.realpath(__file__), 'r').xreadlines():
3711
s = s.strip()
3712
if s.startswith('%'):
3713
mode_cmds.add(re.findall(r'%[a-zA-Z]+', s)[0])
3714
mode_cmds.discard('%s')
3715
for k,v in sage.interfaces.all.__dict__.iteritems():
3716
if isinstance(v, sage.interfaces.expect.Expect):
3717
mode_cmds.add('%'+k)
3718
mode_cmds.update(['%cython', '%time', '%auto', '%hide', '%hideall',
3719
'%fork', '%runfile', '%default_mode', '%typeset_mode'])
3720
v = list(sorted(mode_cmds))
3721
return v
3722
3723
########################################################
3724
# Go mode
3725
########################################################
3726
def go(s):
3727
"""
3728
Run a go program. For example,
3729
3730
%go
3731
func main() { fmt.Println("Hello World") }
3732
3733
You can set the whole worksheet to be in go mode by typing
3734
3735
%default_mode go
3736
3737
NOTES:
3738
3739
- The official Go tutorial as a long Sage Worksheet is available here:
3740
3741
https://github.com/sagemath/cloud-examples/tree/master/go
3742
3743
- There is no relation between one cell and the next. Each is a separate
3744
self-contained go program, which gets compiled and run, with the only
3745
side effects being changes to the filesystem. The program itself is
3746
stored in a random file that is deleted after it is run.
3747
3748
- The %go command automatically adds 'package main' and 'import "fmt"'
3749
(if fmt. is used) to the top of the program, since the assumption
3750
is that you're using %go interactively.
3751
"""
3752
import uuid
3753
name = str(uuid.uuid4())
3754
if 'fmt.' in s and '"fmt"' not in s and "'fmt'" not in s:
3755
s = 'import "fmt"\n' + s
3756
if 'package main' not in s:
3757
s = 'package main\n' + s
3758
try:
3759
open(name +'.go','w').write(s.encode("UTF-8"))
3760
(child_stdin, child_stdout, child_stderr) = os.popen3('go build %s.go'%name)
3761
err = child_stderr.read()
3762
sys.stdout.write(child_stdout.read())
3763
sys.stderr.write(err)
3764
sys.stdout.flush()
3765
sys.stderr.flush()
3766
if not os.path.exists(name): # failed to produce executable
3767
return
3768
(child_stdin, child_stdout, child_stderr) = os.popen3("./" + name)
3769
sys.stdout.write(child_stdout.read())
3770
sys.stderr.write(child_stderr.read())
3771
sys.stdout.flush()
3772
sys.stderr.flush()
3773
finally:
3774
try:
3775
os.unlink(name+'.go')
3776
except:
3777
pass
3778
try:
3779
os.unlink(name)
3780
except:
3781
pass
3782
3783
########################################################
3784
# Java mode
3785
########################################################
3786
def java(s):
3787
"""
3788
Run a Java program. For example,
3789
3790
%java
3791
public class YourName { public static void main(String[] args) { System.out.println("Hello world"); } }
3792
3793
You can set the whole worksheet to be in java mode by typing
3794
3795
%default_mode java
3796
3797
NOTE:
3798
3799
- There is no relation between one cell and the next. Each is a separate
3800
self-contained java program, which gets compiled and run, with the only
3801
side effects being changes to the filesystem. The program itself is
3802
stored in a file named as the public class that is deleted after it is run.
3803
"""
3804
name = re.search('public class (?P<name>[a-zA-Z0-9]+)', s)
3805
if name:
3806
name = name.group('name')
3807
else:
3808
print 'error public class name not found'
3809
return
3810
try:
3811
open(name +'.java','w').write(s.encode("UTF-8"))
3812
(child_stdin, child_stdout, child_stderr) = os.popen3('javac %s.java'%name)
3813
err = child_stderr.read()
3814
sys.stdout.write(child_stdout.read())
3815
sys.stderr.write(err)
3816
sys.stdout.flush()
3817
sys.stderr.flush()
3818
if not os.path.exists(name+'.class'): # failed to produce executable
3819
return
3820
(child_stdin, child_stdout, child_stderr) = os.popen3('java %s'%name)
3821
sys.stdout.write(child_stdout.read())
3822
sys.stderr.write('\n'+child_stderr.read())
3823
sys.stdout.flush()
3824
sys.stderr.flush()
3825
finally:
3826
pass
3827
try:
3828
os.unlink(name+'.java')
3829
except:
3830
pass
3831
try:
3832
os.unlink(name+'.class')
3833
except:
3834
pass
3835
3836
# Julia pexepect interface support
3837
import julia
3838
import sage.interfaces
3839
sage.interfaces.julia = julia # the module
3840
julia = julia.julia # specific instance
3841
sage.interfaces.all.julia = julia
3842
3843
3844
3845
3846
# Help command
3847
import sage.misc.sagedoc
3848
import sage.version
3849
import sage.misc.sagedoc
3850
def help(*args, **kwds):
3851
if len(args) > 0 or len(kwds) > 0:
3852
sage.misc.sagedoc.help(*args, **kwds)
3853
else:
3854
s = """
3855
## Welcome to Sage %s!
3856
3857
- **Online documentation:** [View the Sage documentation online](http://www.sagemath.org/doc/).
3858
3859
- **Help:** For help on any object or function, for example `matrix_plot`, enter `matrix_plot?` followed by tab or shift+enter. For help on any module (or object or function), for example, `sage.matrix`, enter `help(sage.matrix)`.
3860
3861
- **Tab completion:** Type `obj` followed by tab to see all completions of obj. To see all methods you may call on `obj`, type `obj.` followed by tab.
3862
3863
- **Source code:** Enter `matrix_plot??` followed by tab or shift+enter to look at the source code of `matrix_plot`.
3864
3865
- **License information:** For license information about Sage and its components, enter `license()`."""%sage.version.version
3866
salvus.md(s)
3867
3868
# Import the jupyter kernel client.
3869
from sage_jupyter import jupyter
3870
3871
# license() workaround for IPython pager
3872
# could also set os.environ['TERM'] to 'dumb' to workaround the pager
3873
def license():
3874
r"""
3875
Display Sage license file COPYING.txt
3876
3877
You can also view this information in an SMC terminal session:
3878
3879
| $ sage
3880
| sage: license()
3881
3882
"""
3883
print(sage.misc.copying.license)
3884
3885
# search_src
3886
import os
3887
import glob
3888
3889
# from http://stackoverflow.com/questions/9877462/is-there-a-python-equivalent-to-the-which-commane
3890
# in python 3.3+ there is shutil.which()
3891
def which(pgm):
3892
path=os.getenv('PATH')
3893
for p in path.split(os.path.pathsep):
3894
p=os.path.join(p,pgm)
3895
if os.path.exists(p) and os.access(p,os.X_OK):
3896
return p
3897
3898
from sage_server import MAX_CODE_SIZE
3899
def search_src(str, max_chars = MAX_CODE_SIZE):
3900
r"""
3901
Get file names resulting from git grep of smc repo
3902
3903
INPUT:
3904
3905
- ``str`` -- string, expression to search for; will be quoted
3906
- ``max_chars`` -- integer, max characters to display from selected file
3907
3908
OUTPUT:
3909
3910
Interact selector of matching filenames. Choosing one causes its
3911
contents to be shown in salvus.code() output.
3912
"""
3913
sage_cmd = which("sage")
3914
if os.path.islink(sage_cmd):
3915
sage_cmd = os.readlink(sage_cmd)
3916
3917
# /projects/sage/sage-x.y/src/bin
3918
sdir = os.path.dirname(sage_cmd)
3919
3920
# /projects/sage/sage-x.y
3921
sdir = os.path.dirname(os.path.dirname(sdir))
3922
3923
# /projects/sage/sage-x.y/src
3924
sdir = glob.glob(sdir + "/src/sage")[0]
3925
3926
cmd = 'cd %s;timeout 5 git grep -il "%s"'%(sdir, str)
3927
srch = os.popen(cmd).read().splitlines()
3928
header = "files matched"
3929
nftext = header + ": %s"%len(srch)
3930
3931
@interact
3932
def _(fname = selector([nftext]+srch,"view source file:")):
3933
if not fname.startswith(header):
3934
with open(os.path.join(sdir, fname), 'r') as infile:
3935
code = infile.read(max_chars)
3936
salvus.code(code, mode = "python", filename = fname)
3937
3938
# search_doc
3939
def search_doc(str):
3940
r"""
3941
Create link to Google search of sage docs.
3942
3943
INPUT:
3944
3945
- ``str`` -- string, expression to search for; will be quoted
3946
3947
OUTPUT:
3948
3949
HTML hyperlink to google search
3950
"""
3951
txt = 'Use this link to search: ' + \
3952
'<a href="https://www.google.com/search?q=site%3Adoc.sagemath.org+' + \
3953
str + '&oq=site%3Adoc.sagemath.org">'+str+'</a>'
3954
salvus.html(txt)
3955
3956
import sage.misc.session
3957
def show_identifiers():
3958
"""
3959
Returns a list of all variable names that have been defined during this session.
3960
3961
SMC introduces worksheet variables, including 'smc','salvus', 'require', and after reset(), 'sage_salvus'.
3962
These identifiers are removed from the output of sage.misc.session.show_identifiers() on return.
3963
User should not assign to these variables when running code in a worksheet.
3964
"""
3965
si = eval('show_identifiers.fn()',salvus.namespace)
3966
si2 = [v for v in si if v not in ['smc','salvus','require','sage_salvus']]
3967
return si2
3968
3969
show_identifiers.fn = sage.misc.session.show_identifiers
3970
3971