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