passagemath-repl 10.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_repl-10.5.1.data/scripts/sage-cachegrind +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-callgrind +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-cleaner +230 -0
- passagemath_repl-10.5.1.data/scripts/sage-coverage +327 -0
- passagemath_repl-10.5.1.data/scripts/sage-eval +14 -0
- passagemath_repl-10.5.1.data/scripts/sage-fixdoctests +710 -0
- passagemath_repl-10.5.1.data/scripts/sage-inline-fortran +12 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipynb2rst +50 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipython +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-massif +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-notebook +267 -0
- passagemath_repl-10.5.1.data/scripts/sage-omega +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-preparse +302 -0
- passagemath_repl-10.5.1.data/scripts/sage-run +27 -0
- passagemath_repl-10.5.1.data/scripts/sage-run-cython +10 -0
- passagemath_repl-10.5.1.data/scripts/sage-runtests +9 -0
- passagemath_repl-10.5.1.data/scripts/sage-startuptime.py +163 -0
- passagemath_repl-10.5.1.data/scripts/sage-valgrind +34 -0
- passagemath_repl-10.5.1.dist-info/METADATA +77 -0
- passagemath_repl-10.5.1.dist-info/RECORD +162 -0
- passagemath_repl-10.5.1.dist-info/WHEEL +5 -0
- passagemath_repl-10.5.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_repl.py +119 -0
- sage/doctest/__init__.py +4 -0
- sage/doctest/__main__.py +236 -0
- sage/doctest/all.py +4 -0
- sage/doctest/check_tolerance.py +261 -0
- sage/doctest/control.py +1727 -0
- sage/doctest/external.py +534 -0
- sage/doctest/fixtures.py +383 -0
- sage/doctest/forker.py +2665 -0
- sage/doctest/marked_output.py +102 -0
- sage/doctest/parsing.py +1708 -0
- sage/doctest/parsing_test.py +79 -0
- sage/doctest/reporting.py +733 -0
- sage/doctest/rif_tol.py +124 -0
- sage/doctest/sources.py +1657 -0
- sage/doctest/test.py +584 -0
- sage/doctest/tests/1second.rst +4 -0
- sage/doctest/tests/99seconds.rst +4 -0
- sage/doctest/tests/abort.rst +5 -0
- sage/doctest/tests/atexit.rst +7 -0
- sage/doctest/tests/fail_and_die.rst +6 -0
- sage/doctest/tests/initial.rst +15 -0
- sage/doctest/tests/interrupt.rst +7 -0
- sage/doctest/tests/interrupt_diehard.rst +14 -0
- sage/doctest/tests/keyboardinterrupt.rst +11 -0
- sage/doctest/tests/longtime.rst +5 -0
- sage/doctest/tests/nodoctest +5 -0
- sage/doctest/tests/random_seed.rst +4 -0
- sage/doctest/tests/show_skipped.rst +18 -0
- sage/doctest/tests/sig_on.rst +9 -0
- sage/doctest/tests/simple_failure.rst +8 -0
- sage/doctest/tests/sleep_and_raise.rst +106 -0
- sage/doctest/tests/tolerance.rst +31 -0
- sage/doctest/util.py +750 -0
- sage/interfaces/cleaner.py +48 -0
- sage/interfaces/quit.py +163 -0
- sage/misc/all__sagemath_repl.py +51 -0
- sage/misc/banner.py +235 -0
- sage/misc/benchmark.py +221 -0
- sage/misc/classgraph.py +134 -0
- sage/misc/copying.py +22 -0
- sage/misc/cython.py +694 -0
- sage/misc/dev_tools.py +745 -0
- sage/misc/edit_module.py +304 -0
- sage/misc/explain_pickle.py +3079 -0
- sage/misc/gperftools.py +361 -0
- sage/misc/inline_fortran.py +212 -0
- sage/misc/messaging.py +86 -0
- sage/misc/pager.py +21 -0
- sage/misc/profiler.py +179 -0
- sage/misc/python.py +70 -0
- sage/misc/remote_file.py +53 -0
- sage/misc/sage_eval.py +249 -0
- sage/misc/sage_input.py +3621 -0
- sage/misc/sagedoc.py +1742 -0
- sage/misc/sh.py +38 -0
- sage/misc/trace.py +90 -0
- sage/repl/__init__.py +16 -0
- sage/repl/all.py +15 -0
- sage/repl/attach.py +625 -0
- sage/repl/configuration.py +186 -0
- sage/repl/display/__init__.py +1 -0
- sage/repl/display/fancy_repr.py +354 -0
- sage/repl/display/formatter.py +318 -0
- sage/repl/display/jsmol_iframe.py +290 -0
- sage/repl/display/pretty_print.py +153 -0
- sage/repl/display/util.py +163 -0
- sage/repl/image.py +302 -0
- sage/repl/inputhook.py +91 -0
- sage/repl/interface_magic.py +298 -0
- sage/repl/interpreter.py +854 -0
- sage/repl/ipython_extension.py +593 -0
- sage/repl/ipython_kernel/__init__.py +1 -0
- sage/repl/ipython_kernel/__main__.py +4 -0
- sage/repl/ipython_kernel/all_jupyter.py +10 -0
- sage/repl/ipython_kernel/install.py +301 -0
- sage/repl/ipython_kernel/interact.py +278 -0
- sage/repl/ipython_kernel/kernel.py +217 -0
- sage/repl/ipython_kernel/widgets.py +466 -0
- sage/repl/ipython_kernel/widgets_sagenb.py +587 -0
- sage/repl/ipython_tests.py +163 -0
- sage/repl/load.py +326 -0
- sage/repl/preparse.py +2218 -0
- sage/repl/prompts.py +90 -0
- sage/repl/rich_output/__init__.py +4 -0
- sage/repl/rich_output/backend_base.py +648 -0
- sage/repl/rich_output/backend_doctest.py +316 -0
- sage/repl/rich_output/backend_emacs.py +151 -0
- sage/repl/rich_output/backend_ipython.py +596 -0
- sage/repl/rich_output/buffer.py +311 -0
- sage/repl/rich_output/display_manager.py +829 -0
- sage/repl/rich_output/example.avi +0 -0
- sage/repl/rich_output/example.canvas3d +1 -0
- sage/repl/rich_output/example.dvi +0 -0
- sage/repl/rich_output/example.flv +0 -0
- sage/repl/rich_output/example.gif +0 -0
- sage/repl/rich_output/example.jpg +0 -0
- sage/repl/rich_output/example.mkv +0 -0
- sage/repl/rich_output/example.mov +0 -0
- sage/repl/rich_output/example.mp4 +0 -0
- sage/repl/rich_output/example.ogv +0 -0
- sage/repl/rich_output/example.pdf +0 -0
- sage/repl/rich_output/example.png +0 -0
- sage/repl/rich_output/example.svg +54 -0
- sage/repl/rich_output/example.webm +0 -0
- sage/repl/rich_output/example.wmv +0 -0
- sage/repl/rich_output/example_jmol.spt.zip +0 -0
- sage/repl/rich_output/example_wavefront_scene.mtl +7 -0
- sage/repl/rich_output/example_wavefront_scene.obj +17 -0
- sage/repl/rich_output/output_basic.py +391 -0
- sage/repl/rich_output/output_browser.py +103 -0
- sage/repl/rich_output/output_catalog.py +54 -0
- sage/repl/rich_output/output_graphics.py +320 -0
- sage/repl/rich_output/output_graphics3d.py +345 -0
- sage/repl/rich_output/output_video.py +231 -0
- sage/repl/rich_output/preferences.py +432 -0
- sage/repl/rich_output/pretty_print.py +339 -0
- sage/repl/rich_output/test_backend.py +201 -0
- sage/repl/user_globals.py +214 -0
- sage/tests/all.py +0 -0
- sage/tests/all__sagemath_repl.py +3 -0
- sage/tests/article_heuberger_krenn_kropf_fsm-in-sage.py +630 -0
- sage/tests/arxiv_0812_2725.py +351 -0
- sage/tests/benchmark.py +1925 -0
- sage/tests/book_schilling_zabrocki_kschur_primer.py +795 -0
- sage/tests/book_stein_ent.py +651 -0
- sage/tests/book_stein_modform.py +558 -0
- sage/tests/cmdline.py +796 -0
- sage/tests/combinatorial_hopf_algebras.py +52 -0
- sage/tests/finite_poset.py +623 -0
- sage/tests/functools_partial_src.py +27 -0
- sage/tests/gosper-sum.py +218 -0
- sage/tests/lazy_imports.py +28 -0
- sage/tests/modular_group_cohomology.py +80 -0
- sage/tests/numpy.py +21 -0
- sage/tests/parigp.py +76 -0
- sage/tests/startup.py +27 -0
- sage/tests/symbolic-series.py +76 -0
- sage/tests/sympy.py +16 -0
- sage/tests/test_deprecation.py +31 -0
@@ -0,0 +1,301 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-repl
|
2
|
+
"""
|
3
|
+
Installing the SageMath Jupyter Kernel and Extensions
|
4
|
+
|
5
|
+
Kernels have to register themselves with Jupyter so that they appear
|
6
|
+
in the Jupyter notebook's kernel drop-down. This is done by
|
7
|
+
:class:`SageKernelSpec`.
|
8
|
+
|
9
|
+
.. NOTE::
|
10
|
+
|
11
|
+
The doctests in this module run in a temporary directory as the involved
|
12
|
+
directories might be different during runs of the tests and actual
|
13
|
+
installation and because we might be lacking write permission to places
|
14
|
+
such as ``/usr/share``.
|
15
|
+
"""
|
16
|
+
|
17
|
+
import errno
|
18
|
+
import os
|
19
|
+
import warnings
|
20
|
+
|
21
|
+
from sage.env import (
|
22
|
+
SAGE_DOC,
|
23
|
+
SAGE_EXTCODE,
|
24
|
+
SAGE_VENV,
|
25
|
+
SAGE_VERSION,
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class SageKernelSpec:
|
30
|
+
|
31
|
+
def __init__(self, prefix=None):
|
32
|
+
"""
|
33
|
+
Utility to manage SageMath kernels and extensions.
|
34
|
+
|
35
|
+
INPUT:
|
36
|
+
|
37
|
+
- ``prefix`` -- (default: ``sys.prefix``)
|
38
|
+
directory for the installation prefix
|
39
|
+
|
40
|
+
EXAMPLES::
|
41
|
+
|
42
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
43
|
+
sage: prefix = tmp_dir()
|
44
|
+
sage: spec = SageKernelSpec(prefix=prefix)
|
45
|
+
sage: spec._display_name # random output
|
46
|
+
'SageMath 6.9'
|
47
|
+
sage: spec.kernel_dir == SageKernelSpec(prefix=prefix).kernel_dir
|
48
|
+
True
|
49
|
+
"""
|
50
|
+
self._display_name = 'SageMath {0}'.format(SAGE_VERSION)
|
51
|
+
if prefix is None:
|
52
|
+
from sys import prefix
|
53
|
+
jupyter_dir = os.path.join(prefix, "share", "jupyter")
|
54
|
+
self.nbextensions_dir = os.path.join(jupyter_dir, "nbextensions")
|
55
|
+
self.kernel_dir = os.path.join(jupyter_dir, "kernels", self.identifier())
|
56
|
+
self._mkdirs()
|
57
|
+
|
58
|
+
def _mkdirs(self):
|
59
|
+
"""
|
60
|
+
Create necessary parent directories.
|
61
|
+
|
62
|
+
EXAMPLES::
|
63
|
+
|
64
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
65
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
66
|
+
sage: spec._mkdirs()
|
67
|
+
sage: os.path.isdir(spec.nbextensions_dir)
|
68
|
+
True
|
69
|
+
"""
|
70
|
+
def mkdir_p(path):
|
71
|
+
try:
|
72
|
+
os.makedirs(path)
|
73
|
+
except OSError:
|
74
|
+
if not os.path.isdir(path):
|
75
|
+
raise
|
76
|
+
mkdir_p(self.nbextensions_dir)
|
77
|
+
mkdir_p(self.kernel_dir)
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
def identifier(cls):
|
81
|
+
"""
|
82
|
+
Internal identifier for the SageMath kernel.
|
83
|
+
|
84
|
+
OUTPUT: the string ``'sagemath'``
|
85
|
+
|
86
|
+
EXAMPLES::
|
87
|
+
|
88
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
89
|
+
sage: SageKernelSpec.identifier()
|
90
|
+
'sagemath'
|
91
|
+
"""
|
92
|
+
return 'sagemath'
|
93
|
+
|
94
|
+
def symlink(self, src, dst):
|
95
|
+
"""
|
96
|
+
Symlink ``src`` to ``dst``.
|
97
|
+
|
98
|
+
This is not an atomic operation.
|
99
|
+
|
100
|
+
Already-existing symlinks will be deleted, already existing
|
101
|
+
non-empty directories will be kept.
|
102
|
+
|
103
|
+
EXAMPLES::
|
104
|
+
|
105
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
106
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
107
|
+
sage: path = tmp_dir()
|
108
|
+
sage: spec.symlink(os.path.join(path, 'a'), os.path.join(path, 'b'))
|
109
|
+
sage: os.listdir(path)
|
110
|
+
['b']
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
os.remove(dst)
|
114
|
+
except OSError as err:
|
115
|
+
if err.errno == errno.EEXIST:
|
116
|
+
return
|
117
|
+
os.symlink(src, dst)
|
118
|
+
|
119
|
+
def use_local_threejs(self):
|
120
|
+
"""
|
121
|
+
Symlink threejs to the Jupyter notebook.
|
122
|
+
|
123
|
+
EXAMPLES::
|
124
|
+
|
125
|
+
sage: # needs threejs
|
126
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
127
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
128
|
+
sage: spec.use_local_threejs()
|
129
|
+
sage: threejs = os.path.join(spec.nbextensions_dir, 'threejs-sage')
|
130
|
+
sage: os.path.isdir(threejs)
|
131
|
+
True
|
132
|
+
"""
|
133
|
+
from sage.features.threejs import Threejs
|
134
|
+
if not Threejs().is_present():
|
135
|
+
return
|
136
|
+
src = os.path.dirname(os.path.dirname(Threejs().absolute_filename()))
|
137
|
+
dst = os.path.join(self.nbextensions_dir, 'threejs-sage')
|
138
|
+
self.symlink(src, dst)
|
139
|
+
|
140
|
+
def _kernel_cmd(self):
|
141
|
+
"""
|
142
|
+
Helper to construct the SageMath kernel command.
|
143
|
+
|
144
|
+
OUTPUT: list of strings; the command to start a new SageMath kernel
|
145
|
+
|
146
|
+
EXAMPLES::
|
147
|
+
|
148
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
149
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
150
|
+
sage: spec._kernel_cmd()
|
151
|
+
['/.../sage',
|
152
|
+
'--python',
|
153
|
+
'-m',
|
154
|
+
'sage.repl.ipython_kernel',
|
155
|
+
'-f',
|
156
|
+
'{connection_file}']
|
157
|
+
"""
|
158
|
+
return [
|
159
|
+
os.path.join(SAGE_VENV, 'bin', 'sage'),
|
160
|
+
'--python',
|
161
|
+
'-m', 'sage.repl.ipython_kernel',
|
162
|
+
'-f', '{connection_file}',
|
163
|
+
]
|
164
|
+
|
165
|
+
def kernel_spec(self):
|
166
|
+
"""
|
167
|
+
Return the kernel spec as Python dictionary.
|
168
|
+
|
169
|
+
OUTPUT: a dictionary; see the Jupyter documentation for details
|
170
|
+
|
171
|
+
EXAMPLES::
|
172
|
+
|
173
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
174
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
175
|
+
sage: spec.kernel_spec()
|
176
|
+
{'argv': ..., 'display_name': 'SageMath ...', 'language': 'sage'}
|
177
|
+
"""
|
178
|
+
return dict(
|
179
|
+
argv=self._kernel_cmd(),
|
180
|
+
display_name=self._display_name,
|
181
|
+
language='sage',
|
182
|
+
)
|
183
|
+
|
184
|
+
def _install_spec(self):
|
185
|
+
"""
|
186
|
+
Install the SageMath Jupyter kernel.
|
187
|
+
|
188
|
+
EXAMPLES::
|
189
|
+
|
190
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
191
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
192
|
+
sage: spec._install_spec()
|
193
|
+
"""
|
194
|
+
jsonfile = os.path.join(self.kernel_dir, "kernel.json")
|
195
|
+
import json
|
196
|
+
with open(jsonfile, 'w') as f:
|
197
|
+
json.dump(self.kernel_spec(), f)
|
198
|
+
|
199
|
+
def _symlink_resources(self):
|
200
|
+
"""
|
201
|
+
Symlink miscellaneous resources.
|
202
|
+
|
203
|
+
This method symlinks additional resources (like the SageMath
|
204
|
+
documentation) into the SageMath kernel directory. This is
|
205
|
+
necessary to make the help links in the notebook work.
|
206
|
+
|
207
|
+
EXAMPLES::
|
208
|
+
|
209
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
210
|
+
sage: spec = SageKernelSpec(prefix=tmp_dir())
|
211
|
+
sage: spec._install_spec()
|
212
|
+
sage: spec._symlink_resources()
|
213
|
+
"""
|
214
|
+
path = os.path.join(SAGE_EXTCODE, 'notebook-ipython')
|
215
|
+
for filename in os.listdir(path):
|
216
|
+
self.symlink(
|
217
|
+
os.path.join(path, filename),
|
218
|
+
os.path.join(self.kernel_dir, filename)
|
219
|
+
)
|
220
|
+
self.symlink(
|
221
|
+
SAGE_DOC,
|
222
|
+
os.path.join(self.kernel_dir, 'doc')
|
223
|
+
)
|
224
|
+
|
225
|
+
@classmethod
|
226
|
+
def update(cls, *args, **kwds):
|
227
|
+
"""
|
228
|
+
Configure the Jupyter notebook for the SageMath kernel.
|
229
|
+
|
230
|
+
This method does everything necessary to use the SageMath kernel,
|
231
|
+
you should never need to call any of the other methods
|
232
|
+
directly.
|
233
|
+
|
234
|
+
EXAMPLES::
|
235
|
+
|
236
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
237
|
+
sage: SageKernelSpec.update(prefix=tmp_dir())
|
238
|
+
"""
|
239
|
+
instance = cls(*args, **kwds)
|
240
|
+
instance.use_local_threejs()
|
241
|
+
instance._install_spec()
|
242
|
+
instance._symlink_resources()
|
243
|
+
|
244
|
+
@classmethod
|
245
|
+
def check(cls):
|
246
|
+
"""
|
247
|
+
Check that the SageMath kernel can be discovered by its name (sagemath).
|
248
|
+
|
249
|
+
This method issues a warning if it cannot -- either because it is not installed,
|
250
|
+
or it is shadowed by a different kernel of this name, or Jupyter is
|
251
|
+
misconfigured in a different way.
|
252
|
+
|
253
|
+
EXAMPLES::
|
254
|
+
|
255
|
+
sage: from sage.repl.ipython_kernel.install import SageKernelSpec
|
256
|
+
sage: SageKernelSpec.check() # random
|
257
|
+
"""
|
258
|
+
from jupyter_client.kernelspec import NoSuchKernel, get_kernel_spec
|
259
|
+
ident = cls.identifier()
|
260
|
+
try:
|
261
|
+
spec = get_kernel_spec(ident)
|
262
|
+
except NoSuchKernel:
|
263
|
+
warnings.warn(f'no kernel named {ident} is accessible; '
|
264
|
+
'check your Jupyter configuration '
|
265
|
+
'(see https://docs.jupyter.org/en/latest/use/jupyter-directories.html)')
|
266
|
+
else:
|
267
|
+
from pathlib import Path
|
268
|
+
if Path(spec.argv[0]).resolve() != Path(os.path.join(SAGE_VENV, 'bin', 'sage')).resolve():
|
269
|
+
warnings.warn(f'the kernel named {ident} does not seem to correspond to this '
|
270
|
+
'installation of SageMath; check your Jupyter configuration '
|
271
|
+
'(see https://docs.jupyter.org/en/latest/use/jupyter-directories.html)')
|
272
|
+
|
273
|
+
|
274
|
+
def have_prerequisites(debug=True):
|
275
|
+
"""
|
276
|
+
Check that we have all prerequisites to run the Jupyter notebook.
|
277
|
+
|
278
|
+
In particular, the Jupyter notebook requires OpenSSL whether or
|
279
|
+
not you are using https. See :issue:`17318`.
|
280
|
+
|
281
|
+
INPUT:
|
282
|
+
|
283
|
+
- ``debug`` -- boolean (default: ``True``); whether to print debug
|
284
|
+
information in case that prerequisites are missing
|
285
|
+
|
286
|
+
OUTPUT: boolean
|
287
|
+
|
288
|
+
EXAMPLES::
|
289
|
+
|
290
|
+
sage: from sage.repl.ipython_kernel.install import have_prerequisites
|
291
|
+
sage: have_prerequisites(debug=False) in [True, False]
|
292
|
+
True
|
293
|
+
"""
|
294
|
+
try:
|
295
|
+
from notebook.notebookapp import NotebookApp
|
296
|
+
return True
|
297
|
+
except ImportError:
|
298
|
+
if debug:
|
299
|
+
import traceback
|
300
|
+
traceback.print_exc()
|
301
|
+
return False
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-repl
|
2
|
+
r"""
|
3
|
+
Interacts for the Sage Jupyter notebook
|
4
|
+
|
5
|
+
This is mostly the same as the stock ``ipywidgets.interact``, but with
|
6
|
+
some customizations for Sage.
|
7
|
+
|
8
|
+
TESTS:
|
9
|
+
|
10
|
+
We need to setup a proper test environment for widgets::
|
11
|
+
|
12
|
+
sage: from ipywidgets.widgets.tests.utils import setup_test_comm
|
13
|
+
sage: setup_test_comm()
|
14
|
+
|
15
|
+
EXAMPLES::
|
16
|
+
|
17
|
+
sage: from sage.repl.ipython_kernel.interact import interact
|
18
|
+
sage: @interact
|
19
|
+
....: def f(x=(0, 10)):
|
20
|
+
....: pass
|
21
|
+
...Interactive function <function f at ...> with 1 widget
|
22
|
+
x: IntSlider(value=5, description='x', max=10)
|
23
|
+
sage: f.widget.children
|
24
|
+
(IntSlider(value=5, description='x', max=10), Output())
|
25
|
+
"""
|
26
|
+
|
27
|
+
# ****************************************************************************
|
28
|
+
# Copyright (C) 2017 Jeroen Demeyer <jdemeyer@cage.ugent.be>
|
29
|
+
#
|
30
|
+
# This program is free software: you can redistribute it and/or modify
|
31
|
+
# it under the terms of the GNU General Public License as published by
|
32
|
+
# the Free Software Foundation, either version 2 of the License, or
|
33
|
+
# (at your option) any later version.
|
34
|
+
# https://www.gnu.org/licenses/
|
35
|
+
# ****************************************************************************
|
36
|
+
|
37
|
+
from collections import OrderedDict
|
38
|
+
from collections.abc import Iterable, Iterator
|
39
|
+
|
40
|
+
from ipywidgets.widgets import SelectionSlider, ValueWidget, ToggleButtons
|
41
|
+
from ipywidgets.widgets.interaction import interactive, signature
|
42
|
+
|
43
|
+
import sage.rings.abc
|
44
|
+
|
45
|
+
from sage.misc.lazy_import import lazy_import
|
46
|
+
from sage.repl.ipython_kernel.widgets import EvalText, SageColorPicker
|
47
|
+
from sage.structure.element import Matrix, parent
|
48
|
+
|
49
|
+
lazy_import("sage.plot.colors", "Color")
|
50
|
+
|
51
|
+
|
52
|
+
class sage_interactive(interactive):
|
53
|
+
"""
|
54
|
+
Wrapper around the ipywidgets interactive which handles some SageNB
|
55
|
+
specifics.
|
56
|
+
|
57
|
+
EXAMPLES::
|
58
|
+
|
59
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
60
|
+
sage: def myfunc(x=10, y='hello', z=None): pass
|
61
|
+
sage: sage_interactive(myfunc, x=(0,100), z=["one", "two", "three"])
|
62
|
+
...Interactive function <function myfunc at ...> with 3 widgets
|
63
|
+
x: IntSlider(value=10, description='x')
|
64
|
+
y: Text(value='hello', description='y')
|
65
|
+
z: Dropdown(description='z', options=('one', 'two', 'three'), value=None)
|
66
|
+
"""
|
67
|
+
def __init__(self, *args, **kwds):
|
68
|
+
"""
|
69
|
+
See :class:`ipywidgets.widgets.interaction.interactive`.
|
70
|
+
|
71
|
+
TESTS::
|
72
|
+
|
73
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
74
|
+
sage: def myfunc(): pass
|
75
|
+
sage: sage_interactive(myfunc, dict(manual=True))
|
76
|
+
Manual interactive function <function myfunc ...> with 0 widgets
|
77
|
+
|
78
|
+
::
|
79
|
+
|
80
|
+
sage: def myfunc(auto_update=False): pass
|
81
|
+
sage: sage_interactive(myfunc)
|
82
|
+
...Manual interactive function <function myfunc ...> with 0 widgets
|
83
|
+
sage: def myfunc(auto_update=None): pass
|
84
|
+
sage: sage_interactive(myfunc)
|
85
|
+
...Interactive function <function myfunc ...> with 0 widgets
|
86
|
+
"""
|
87
|
+
# Use *args to avoid name clash with keyword arguments
|
88
|
+
if len(args) < 2:
|
89
|
+
f = args[0]
|
90
|
+
options = {}
|
91
|
+
else:
|
92
|
+
(f, options) = args
|
93
|
+
options = options.copy()
|
94
|
+
|
95
|
+
# Check for auto_update in signature
|
96
|
+
sig = signature(f)
|
97
|
+
params = OrderedDict(sig.parameters)
|
98
|
+
try:
|
99
|
+
p_auto_update = params.pop("auto_update")
|
100
|
+
except KeyError:
|
101
|
+
pass
|
102
|
+
else:
|
103
|
+
options["manual"] = (p_auto_update.default is False)
|
104
|
+
|
105
|
+
self.__signature = sig.replace(parameters=params.values())
|
106
|
+
super().__init__(f, options, **kwds)
|
107
|
+
if self.manual:
|
108
|
+
# In Sage, manual interacts are always run once
|
109
|
+
self.on_widget_constructed(self.update)
|
110
|
+
else:
|
111
|
+
# In automatic mode, clicking on a ToggleButtons button
|
112
|
+
# should also run the interact
|
113
|
+
for widget in self.kwargs_widgets:
|
114
|
+
if isinstance(widget, ToggleButtons):
|
115
|
+
widget.on_msg(self.update)
|
116
|
+
|
117
|
+
def __repr__(self):
|
118
|
+
"""
|
119
|
+
Textual representation of this interactive function.
|
120
|
+
|
121
|
+
EXAMPLES::
|
122
|
+
|
123
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
124
|
+
sage: def myfunc(): pass
|
125
|
+
sage: sage_interactive(myfunc)
|
126
|
+
...Interactive function <function myfunc ...> with 0 widgets
|
127
|
+
"""
|
128
|
+
s = "Manual interactive" if self.manual else "Interactive"
|
129
|
+
widgets = [w for w in self.children if isinstance(w, ValueWidget)]
|
130
|
+
n = len(widgets)
|
131
|
+
s += " function %r with %s widget%s" % (self.f, n,
|
132
|
+
"s" if n != 1 else "")
|
133
|
+
for w in widgets:
|
134
|
+
s += "\n %s: %s" % (w._kwarg, w)
|
135
|
+
return s
|
136
|
+
|
137
|
+
def signature(self):
|
138
|
+
"""
|
139
|
+
Return the fixed signature of the interactive function (after
|
140
|
+
a possible ``auto_update`` parameter was removed).
|
141
|
+
|
142
|
+
EXAMPLES::
|
143
|
+
|
144
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
145
|
+
sage: def myfunc(x=[1,2,3], auto_update=False): pass
|
146
|
+
sage: sage_interactive(myfunc).signature().parameters
|
147
|
+
...mappingproxy({'x': <Parameter "x=[1, 2, 3]">})
|
148
|
+
"""
|
149
|
+
return self.__signature
|
150
|
+
|
151
|
+
@classmethod # Behaves like a staticmethod, but we need super()
|
152
|
+
def widget_from_single_value(cls, abbrev, *args, **kwds):
|
153
|
+
"""
|
154
|
+
Convert a single value (i.e. a non-iterable) to a widget.
|
155
|
+
|
156
|
+
This supports the Sage :class:`Color` and ``Matrix`` classes.
|
157
|
+
Any unknown type is changed to a string for evaluating.
|
158
|
+
This is meant to support symbolic expressions like ``sin(x)``.
|
159
|
+
|
160
|
+
EXAMPLES::
|
161
|
+
|
162
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
163
|
+
sage: sage_interactive.widget_from_single_value("sin(x)")
|
164
|
+
...Text(value='sin(x)')
|
165
|
+
sage: sage_interactive.widget_from_single_value(sin(x)) # needs sage.symbolic
|
166
|
+
...EvalText(value='sin(x)')
|
167
|
+
sage: sage_interactive.widget_from_single_value(matrix([[1, 2], [3, 4]])) # needs sage.modules
|
168
|
+
...Grid(value=[[1, 2], [3, 4]], children=(Label(value=''), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='3', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='2', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em'))))))
|
169
|
+
sage: from sage.plot.colors import Color # needs sage.plot
|
170
|
+
sage: sage_interactive.widget_from_single_value(Color('cornflowerblue')) # needs sage.plot
|
171
|
+
...SageColorPicker(value='#6495ed')
|
172
|
+
"""
|
173
|
+
# Support Sage matrices and colors
|
174
|
+
if isinstance(abbrev, Matrix):
|
175
|
+
from .widgets_sagenb import input_grid
|
176
|
+
|
177
|
+
return input_grid(abbrev.nrows(), abbrev.ncols(),
|
178
|
+
default=abbrev.list(), to_value=abbrev.parent())
|
179
|
+
|
180
|
+
try:
|
181
|
+
from sage.plot.colors import Color
|
182
|
+
except ImportError:
|
183
|
+
pass
|
184
|
+
else:
|
185
|
+
if isinstance(abbrev, Color):
|
186
|
+
return SageColorPicker(value=abbrev.html_color())
|
187
|
+
# Get widget from IPython if possible
|
188
|
+
widget = super().widget_from_single_value(abbrev, *args, **kwds)
|
189
|
+
if widget is not None or isinstance(abbrev, Iterable):
|
190
|
+
return widget
|
191
|
+
# If IPython didn't construct a widget and the abbrev is not an
|
192
|
+
# iterable, return an EvalText widget
|
193
|
+
return EvalText(value=str(abbrev))
|
194
|
+
|
195
|
+
@classmethod # Behaves like a staticmethod, but we need super()
|
196
|
+
def widget_from_tuple(cls, abbrev, *args, **kwds):
|
197
|
+
"""
|
198
|
+
Convert a tuple to a widget.
|
199
|
+
|
200
|
+
This supports two SageNB extensions: ``(description, abbrev)``
|
201
|
+
if ``description`` is a string and ``(default, abbrev)`` if
|
202
|
+
``abbrev`` is not a single value.
|
203
|
+
|
204
|
+
Symbolic expressions are changed to a floating-point number.
|
205
|
+
|
206
|
+
EXAMPLES::
|
207
|
+
|
208
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
209
|
+
sage: sage_interactive.widget_from_tuple( (0, 10) )
|
210
|
+
...IntSlider(value=5, max=10)
|
211
|
+
sage: sage_interactive.widget_from_tuple( ("number", (0, 10)) )
|
212
|
+
...IntSlider(value=5, description='number', max=10)
|
213
|
+
sage: sage_interactive.widget_from_tuple( (3, (0, 10)) )
|
214
|
+
...IntSlider(value=3, max=10)
|
215
|
+
sage: sage_interactive.widget_from_tuple((2, [('one', 1), ('two', 2), ('three', 3)]))
|
216
|
+
...Dropdown(index=1, options=(('one', 1), ('two', 2), ('three', 3)), value=2)
|
217
|
+
sage: sage_interactive.widget_from_tuple( (sqrt(2), pi) ) # needs sage.symbolic
|
218
|
+
...FloatSlider(value=2.277903107981444, max=3.141592653589793, min=1.4142135623730951)
|
219
|
+
|
220
|
+
TESTS:
|
221
|
+
|
222
|
+
Symbolic subrings::
|
223
|
+
|
224
|
+
sage: SCR = SR.subring(no_variables=True) # needs sage.symbolic
|
225
|
+
sage: sage_interactive.widget_from_tuple( (SCR(sqrt(2)), SCR(pi)) ) # needs sage.symbolic
|
226
|
+
...FloatSlider(value=2.277903107981444, max=3.141592653589793, min=1.4142135623730951)
|
227
|
+
"""
|
228
|
+
# Support (description, abbrev)
|
229
|
+
if len(abbrev) == 2 and isinstance(abbrev[0], str):
|
230
|
+
widget = cls.widget_from_abbrev(abbrev[1])
|
231
|
+
widget.description = abbrev[0]
|
232
|
+
return widget
|
233
|
+
# Support (default, abbrev)
|
234
|
+
if len(abbrev) == 2 and isinstance(abbrev[1], Iterable):
|
235
|
+
widget = cls.widget_from_abbrev(abbrev[1])
|
236
|
+
widget.value = abbrev[0]
|
237
|
+
return widget
|
238
|
+
# Numerically evaluate symbolic expressions
|
239
|
+
|
240
|
+
def n(x):
|
241
|
+
if isinstance(parent(x), sage.rings.abc.SymbolicRing):
|
242
|
+
return x.numerical_approx()
|
243
|
+
else:
|
244
|
+
return x
|
245
|
+
abbrev = tuple(n(x) for x in abbrev)
|
246
|
+
return super().widget_from_tuple(abbrev, *args, **kwds)
|
247
|
+
|
248
|
+
@classmethod # Behaves like a staticmethod, but we need super()
|
249
|
+
def widget_from_iterable(cls, abbrev, *args, **kwds):
|
250
|
+
"""
|
251
|
+
Convert an unspecified iterable to a widget.
|
252
|
+
|
253
|
+
This behaves like in ipywidgets, except that an iterator (like
|
254
|
+
a generator object) becomes a ``SelectionSlider``.
|
255
|
+
|
256
|
+
EXAMPLES::
|
257
|
+
|
258
|
+
sage: from sage.repl.ipython_kernel.interact import sage_interactive
|
259
|
+
sage: sage_interactive.widget_from_iterable([1..5])
|
260
|
+
...Dropdown(options=(1, 2, 3, 4, 5), value=1)
|
261
|
+
sage: sage_interactive.widget_from_iterable(iter([1..5]))
|
262
|
+
...SelectionSlider(options=(1, 2, 3, 4, 5), value=1)
|
263
|
+
sage: sage_interactive.widget_from_iterable((1..5))
|
264
|
+
...SelectionSlider(options=(1, 2, 3, 4, 5), value=1)
|
265
|
+
sage: sage_interactive.widget_from_iterable(x for x in [1..5])
|
266
|
+
...SelectionSlider(options=(1, 2, 3, 4, 5), value=1)
|
267
|
+
sage: def gen():
|
268
|
+
....: yield 1; yield 2; yield 3; yield 4; yield 5
|
269
|
+
sage: sage_interactive.widget_from_iterable(gen())
|
270
|
+
...SelectionSlider(options=(1, 2, 3, 4, 5), value=1)
|
271
|
+
"""
|
272
|
+
if isinstance(abbrev, Iterator):
|
273
|
+
return SelectionSlider(options=list(abbrev))
|
274
|
+
return super().widget_from_iterable(abbrev, *args, **kwds)
|
275
|
+
|
276
|
+
|
277
|
+
# @interact decorator
|
278
|
+
interact = sage_interactive.factory()
|