├── VERSION ├── docs ├── source │ ├── SageViewEditor.dia │ ├── SageViewEditor.png │ ├── widgets.rst │ ├── sage_explorer.rst │ ├── explored_member.rst │ ├── index.rst │ └── conf.py ├── diagram │ ├── sage-explorer-interactivity.pdf │ └── sage-explorer-interactivity.tex └── Makefile ├── .gitignore ├── sage_explorer ├── __init__.py ├── _sage_catalog.py ├── properties.yml ├── _widgets.py ├── explored_member.py └── sage_explorer.py ├── MANIFEST.in ├── Dockerfile ├── makefile ├── ChangeLog ├── tests ├── test_explorer.ipynb ├── test_settings.ipynb ├── test_ExplorerProperties.ipynb ├── test_ExplorableValue.ipynb ├── test_ExplorerMethodSearch.ipynb ├── test_ExplorerHelp.ipynb ├── test_ExplorerTitle.ipynb ├── Mock-Up of sage-explorer with ipyvuetify.ipynb ├── .ipynb_checkpoints │ └── Mock-Up of sage-explorer with ipyvuetify-checkpoint.ipynb └── test_python3.ipynb ├── README.rst ├── setup.py ├── demo_sage_explorer.ipynb └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.3 -------------------------------------------------------------------------------- /docs/source/SageViewEditor.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-explorer/master/docs/source/SageViewEditor.dia -------------------------------------------------------------------------------- /docs/source/SageViewEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-explorer/master/docs/source/SageViewEditor.png -------------------------------------------------------------------------------- /docs/diagram/sage-explorer-interactivity.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagemath/sage-explorer/master/docs/diagram/sage-explorer-interactivity.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | tests/.ipynb_checkpoints/ 3 | *.pyc 4 | sage_explorer.egg-info/ 5 | TODO* 6 | test*.ipynb 7 | build/ 8 | dist/ 9 | examples/ 10 | Untitled*ipynb 11 | DEBUG 12 | -------------------------------------------------------------------------------- /docs/source/widgets.rst: -------------------------------------------------------------------------------- 1 | .. nodoctest 2 | .. autodoc_member_order: 'bysource' 3 | 4 | _widgets 5 | ======== 6 | 7 | .. automodule:: sage_explorer._widgets 8 | :members: 9 | :special-members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /docs/source/sage_explorer.rst: -------------------------------------------------------------------------------- 1 | .. nodoctest 2 | .. autodoc_member_order: 'bysource' 3 | 4 | Sage Explorer 5 | ============= 6 | 7 | .. automodule:: sage_explorer.sage_explorer 8 | :members: 9 | :special-members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /docs/source/explored_member.rst: -------------------------------------------------------------------------------- 1 | .. nodoctest 2 | .. autodoc_member_order: 'bysource' 3 | 4 | Explorered Member 5 | ================= 6 | 7 | .. automodule:: sage_explorer.explored_member 8 | :members: 9 | :special-members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /sage_explorer/__init__.py: -------------------------------------------------------------------------------- 1 | # # Patch the Sage library 2 | # import sys, logging 3 | # from recursive_monkey_patch import monkey_patch 4 | # sage_explorer = sys.modules[__name__] 5 | # import misc 6 | # import sage 7 | # monkey_patch(sage_explorer.misc, sage.misc, log_level=logging.INFO) 8 | 9 | from .sage_explorer import SageExplorer, SageExplorer as explore, ExplorerSettings, ExplorerSettings as Settings 10 | try: 11 | from ._widgets import * 12 | except: 13 | pass 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include ChangeLog 2 | include LICENSE 3 | include README.rst 4 | include VERSION 5 | 6 | # Documentation 7 | include demo_sage_explorer.ipynb 8 | graft docs 9 | exclude docs/\#* 10 | prune docs/build 11 | prune docs/dist 12 | prune docs/presentation 13 | prune docs/video 14 | 15 | # Examples 16 | prune examples 17 | 18 | # Tests 19 | prune tests 20 | include tests/test_settings.ipynb 21 | 22 | # Patterns to exclude from any directory 23 | global-exclude *~ 24 | global-exclude *.pyc 25 | global-exclude *.pyo 26 | global-exclude .git 27 | global-exclude *checkpoint* 28 | global-exclude *Untitled* 29 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Sage Explorer 3 | ============= 4 | 5 | A `Jupyter Notebook `_ widget for exploring `SageMath `_ objects. 6 | 7 | To use this module, you need to import it:: 8 | 9 | from sage_explorer import explore 10 | 11 | This work is licensed under a `Creative Commons Attribution-Share Alike 12 | 3.0 License`__. 13 | 14 | __ https://creativecommons.org/licenses/by-sa/3.0/ 15 | 16 | Sage Explorer 17 | ============= 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | :glob: 22 | 23 | * 24 | 25 | Indices and Tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for binder 2 | FROM sagemath/sagemath:9.1.rc5 3 | USER root 4 | ENV HOME /root 5 | RUN apt-get update && apt-get -qq install -y curl tar \ 6 | && curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - \ 7 | && apt-get install -yq nodejs && npm install npm@latest -g 8 | USER sage 9 | ENV HOME /home/sage 10 | RUN sage -pip install jupyterlab==1.2.11 11 | RUN sage -pip install --upgrade ipywidgets sage_combinat_widgets pyyaml ipyevents ipympl 12 | RUN sage -jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager \ 13 | && sage -jupyter lab build \ 14 | && sage -jupyter lab clean 15 | COPY --chown=sage:sage . ${HOME}/sage-explorer 16 | WORKDIR ${HOME}/sage-explorer 17 | RUN sage -pip install . 18 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # This Makefile is for convenience as a reminder and shortcut for the most used commands 2 | 3 | # Package folder 4 | PACKAGE = sage_explorer 5 | 6 | # change to your sage command if needed 7 | SAGE = sage 8 | 9 | all: install test 10 | 11 | install: 12 | $(SAGE) -pip install --upgrade --no-index -v . 13 | 14 | uninstall: 15 | $(SAGE) -pip uninstall . 16 | 17 | develop: 18 | $(SAGE) -pip install --upgrade -e . 19 | 20 | test: 21 | $(SAGE) setup.py test 22 | 23 | coverage: 24 | $(SAGE) -coverage $(PACKAGE)/* 25 | 26 | doc: 27 | cd docs && $(SAGE) -sh -c "make html" 28 | 29 | doc-pdf: 30 | cd docs && $(SAGE) -sh -c "make latexpdf" 31 | 32 | clean: clean-doc 33 | 34 | clean-doc: 35 | cd docs && $(SAGE) -sh -c "make clean" 36 | 37 | .PHONY: all install develop test coverage clean clean-doc doc doc-pdf 38 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 0.5.3 2 | * Settings API improved 3 | * Explorer interactivity small improvements 4 | * Display details, bug fixes & version upgrades 5 | 0.5.2 6 | * Exploring catalogs 7 | * Simple plot for functions of 1 variable 8 | 0.5.1 9 | * Configurable settings for properties lists and display options 10 | * Bug fixes 11 | 0.5.0 12 | * Complete redesign: lighter ergonomics 13 | * Complete redesign: modularity in code 14 | 0.4.3 15 | * Compatibility with Python3 16 | 0.4.2 17 | * Relative import fix 18 | * CSS code for wider menus 19 | 0.4.1 20 | * Minor updates for Python3 compatibility 21 | * Additional properties 22 | 0.4.0 23 | * Avoided all direct Sagemath dependencies 24 | * Restructured introspection methods 25 | * Support for Skew Tableau and Skew Partition 26 | * Widget interactivity is now reflected in the underlying math object 27 | -------------------------------------------------------------------------------- /tests/test_explorer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sage_explorer import explore\n", 10 | "from sage.combinat.tableau import StandardTableaux\n", 11 | "t = StandardTableaux(15).random_element()\n", 12 | "explore(t)" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "#from sage_explorer.sage_explorer import Settings\n", 22 | "#Settings.__dict__" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3", 29 | "language": "python", 30 | "name": "python3" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.7.3" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 4 47 | } 48 | -------------------------------------------------------------------------------- /tests/test_settings.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sage_explorer import *\n", 10 | "settings = Settings()\n", 11 | "settings.properties['cardinality']" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "settings.add_property('cardinality', instance_of=frozenset)\n", 21 | "settings.properties['cardinality']" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "settings.remove_property('cardinality', instance_of=frozenset)\n", 31 | "settings.properties['cardinality']" 32 | ] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "SageMath 9.1.rc5", 38 | "language": "sage", 39 | "name": "sagemath" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.7.3" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 2 56 | } 57 | -------------------------------------------------------------------------------- /tests/test_ExplorerProperties.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from ipywidgets import HTML, HTMLMath,Label\n", 10 | "HTMLMath(\"$$ok$$\")" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from new_sage_explorer.new_sage_explorer import ExplorerProperties\n", 20 | "from sage.symbolic.constants import pi as c_pi\n", 21 | "ExplorerProperties(c_pi)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "t = StandardTableaux(15).random_element()\n", 31 | "ExplorerProperties(t)\n", 32 | "#ExplorableValue(t.add_entry)" 33 | ] 34 | } 35 | ], 36 | "metadata": { 37 | "kernelspec": { 38 | "display_name": "SageMath 8.6", 39 | "language": "", 40 | "name": "sagemath" 41 | }, 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.6.6" 53 | } 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 2 57 | } 58 | -------------------------------------------------------------------------------- /tests/test_ExplorableValue.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from ipywidgets import HTML, HTMLMath,Label\n", 10 | "HTMLMath(\"$$ok$$\")" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from new_sage_explorer.new_sage_explorer import ExplorableValue\n", 20 | "from sage.symbolic.constants import pi as c_pi\n", 21 | "ExplorableValue(c_pi)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "t = StandardTableaux(15).random_element()\n", 31 | "ExplorableValue(t)\n", 32 | "#ExplorableValue(t.add_entry)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "HTMLMath(str(t))" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "SageMath 8.6", 48 | "language": "", 49 | "name": "sagemath" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.6.6" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 2 66 | } 67 | -------------------------------------------------------------------------------- /tests/test_ExplorerMethodSearch.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from ipywidgets import Combobox\n", 10 | "from traitlets import Any\n", 11 | "class ExplorerMethodSearchLoc(Combobox):\n", 12 | " r\"\"\" \n", 13 | " A widget to search a method \n", 14 | " \"\"\"\n", 15 | " value = Any()\n", 16 | "\n", 17 | " def __init__(self, obj):\n", 18 | " super(ExplorerMethodSearchLoc, self).__init__()\n", 19 | " self.value = obj\n", 20 | "\n" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "t = StandardTableaux(5).random_element()\n", 30 | "ExplorerMethodSearchLoc(t)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "from new_sage_explorer.new_sage_explorer import ExplorerMethodSearch\n", 40 | "ExplorerMethodSearch(t)\n", 41 | "#ExplorerMethodSearch(t.__class__)" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "SageMath 8.6", 48 | "language": "", 49 | "name": "sagemath" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.6.6" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 2 66 | } 67 | -------------------------------------------------------------------------------- /tests/test_ExplorerHelp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sage.misc.sphinxify import sphinxify\n", 10 | "#from sagenb.misc.sphinxify import sphinxify\n", 11 | "#s = Partition.__doc__\n", 12 | "from sage.symbolic.constants import pi as c_pi\n", 13 | "s = c_pi._latex_()\n", 14 | "s\n", 15 | "ss = sphinxify(s)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "from ipywidgets import *\n", 25 | "#HTML(ss)\n", 26 | "HTMLMath(\"$$%s$$\" % s)" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "h = '$$ ' + s + ' $$'\n", 36 | "HTML(h)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "from new_sage_explorer.new_sage_explorer import ExplorerHelp\n", 46 | "t = StandardTableaux(5).random_element()\n", 47 | "w = ExplorerHelp(t)\n", 48 | "w" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "w.children[0].value" 58 | ] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "SageMath 8.6", 64 | "language": "", 65 | "name": "sagemath" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 3 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython3", 77 | "version": "3.6.6" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 2 82 | } 83 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Sage Explorer 3 | ============= 4 | 5 | .. image:: https://mybinder.org/badge.svg 6 | :target: https://mybinder.org/v2/gh/sagemath/sage-explorer/master 7 | 8 | A `Jupyter Notebook `_ widget for exploring `SageMath `_ objects. 9 | 10 | 11 | Installation 12 | ------------ 13 | 14 | Try it on binder 15 | ^^^^^^^^^^^^^^^^ 16 | 17 | `demo `_ 18 | 19 | 20 | Local install from source 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | Download the source from the git repository:: 24 | 25 | $ git clone https://github.com/sagemath/sage-explorer.git 26 | 27 | Change to the root directory and run:: 28 | 29 | $ sage -pip install --upgrade --no-index -v . 30 | 31 | For convenience this package contains a [makefile](makefile) with this 32 | and other often used commands. Should you wish too, you can use the 33 | shorthand:: 34 | 35 | $ make install 36 | 37 | Install from PyPI 38 | ^^^^^^^^^^^^^^^^^^ 39 | 40 | Sage Explorer is distributed on PyPI. 41 | 42 | $ sage -pip install sage_explorer 43 | 44 | 45 | Usage 46 | ----- 47 | 48 | See the `demo notebook `_. 49 | 50 | How to contribute 51 | ----------------- 52 | 53 | The most practical process for contributions is to 54 | 55 | # open a ticket to describe what you intend to do 56 | # write your patch 57 | # make a pull request 58 | 59 | Please PR to the branch `develop` as the branch `master` holds only stable code. 60 | 61 | 62 | Acknowledgments 63 | --------------- 64 | 65 | .. |EULogo| image:: http://opendreamkit.org/public/logos/Flag_of_Europe.svg 66 | :width: 25 67 | :alt: EU logo 68 | 69 | * |EULogo| This package was created under funding of the Horizon 2020 European Research Infrastructure project 70 | `OpenDreamKit `_ (grant agreement `#676541 `_). 71 | 72 | * `Nathan Carter `_ offered inspiring insights for the new 0.5.0 design. 73 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ## -*- encoding: utf-8 -*- 2 | import os 3 | import sys 4 | from setuptools import setup 5 | from codecs import open # To open the README file with proper encoding 6 | from setuptools.command.test import test as TestCommand # for tests 7 | 8 | # The name of the project 9 | name = 'sage-explorer' 10 | 11 | # Ensure python>=3.6 12 | if sys.version_info.major < 3 or sys.version_info.minor < 6: 13 | raise ValueError("Python version '%s.%s' unsupported" % ( 14 | sys.version_info.major, sys.version_info.minor) 15 | ) 16 | 17 | # Get information from separate files (README, VERSION) 18 | def readfile(filename): 19 | with open(filename, encoding='utf-8') as f: 20 | return f.read() 21 | 22 | # For the tests 23 | class SageTest(TestCommand): 24 | def run_tests(self): 25 | errno = os.system("sage -t --force-lib sage_explorer") 26 | if errno != 0: 27 | sys.exit(1) 28 | 29 | setup( 30 | name = name, 31 | version = readfile("VERSION"), 32 | description='Jupyter explorer widget for SAGE objects', 33 | long_description = readfile("README.rst"), 34 | url='https://github.com/sagemath/sage-explorer', 35 | author='Odile Bénassy, Nicolas M. Thiéry', 36 | author_email='odile.benassy@u-psud.fr', 37 | license='GPLv2+', 38 | classifiers=[ 39 | # How mature is this project? Common values are 40 | # 3 - Alpha 41 | # 4 - Beta 42 | # 5 - Production/Stable 43 | 'Development Status :: 3 - Alpha', 44 | 'Intended Audience :: Science/Research', 45 | 'Topic :: Scientific/Engineering :: Mathematics', 46 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 47 | 'Programming Language :: Python :: 3', 48 | 'Programming Language :: Python :: 3.6', 49 | 'Programming Language :: Python :: 3.7', 50 | 'Framework :: Jupyter', 51 | ], # classifiers list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 52 | package_data = {'sage_explorer': ['*.yml']}, 53 | keywords = "SageMath widget explorer jupyter notebook", 54 | packages = ['sage_explorer'], 55 | cmdclass = {'test': SageTest}, # adding a special setup command for tests 56 | install_requires = ['PyYAML', 'cysignals', 'ipywidgets >= 7.5.0', 'ipyevents', 'sage-combinat-widgets'], 57 | extra_require = ['sage-package', 'sphinx'] 58 | ) 59 | -------------------------------------------------------------------------------- /sage_explorer/_sage_catalog.py: -------------------------------------------------------------------------------- 1 | """Catalogs for Index Page""" 2 | _name = "Sage Catalog" 3 | 4 | from sage.groups.affine_gps import catalog as affine_groups_catalog 5 | from sage.groups.groups_catalog import presentation as presentation_groups_catalog 6 | from sage.groups.perm_gps import permutation_groups_catalog as permutation_groups_catalog 7 | from sage.groups.matrix_gps import catalog as matrix_groups_catalog 8 | from sage.groups.misc_gps import misc_groups_catalog 9 | from sage.algebras import catalog as algebras_catalog 10 | from sage.combinat.posets.poset_examples import Posets as posets_catalog 11 | from sage.monoids import all as monoids_catalog 12 | from sage.graphs.graph_generators import graphs as graphs_catalog 13 | from sage.modules import all as modules_catalog 14 | from sage.matroids import catalog as matroids_catalog 15 | from sage.combinat.crystals import catalog as crystals_catalog 16 | from sage.coding import codes_catalog 17 | from sage.game_theory.catalog import normal_form_games as games_catalog 18 | from sage.combinat.words import word_generators as words_catalog 19 | 20 | class fields_catalog: 21 | r"""A catalog of fields.""" 22 | from sage.rings.finite_rings.finite_field_constructor import FiniteField 23 | from sage.rings.complex_field import ComplexField 24 | from sage.rings.rational_field import RationalField 25 | from sage.rings.real_mpfr import RealField 26 | from sage.rings.qqbar import AlgebraicRealField, AlgebraicField 27 | 28 | presentation_groups_catalog._name = "Groups given by presentation" 29 | permutation_groups_catalog._name = "Permutation groups" 30 | matrix_groups_catalog._name = "Matrix groups" 31 | affine_groups_catalog._name = "Affine groups" 32 | misc_groups_catalog._name = "Misc groups" 33 | monoids_catalog._name = "Monoids" 34 | fields_catalog._name = "Fields" 35 | algebras_catalog._name = "Algebras" 36 | modules_catalog._name = "Modules" 37 | graphs_catalog._name = "Graphs" 38 | posets_catalog._name = "Posets" 39 | crystals_catalog._name = "Crystals" 40 | codes_catalog._name = "Codes" 41 | matroids_catalog._name = "Matroids" 42 | games_catalog._name = "Games" 43 | words_catalog._name = "Words" 44 | 45 | sage_catalogs = [ 46 | presentation_groups_catalog, 47 | permutation_groups_catalog, 48 | matrix_groups_catalog, 49 | affine_groups_catalog, 50 | misc_groups_catalog, 51 | monoids_catalog, 52 | fields_catalog, 53 | algebras_catalog, 54 | modules_catalog, 55 | graphs_catalog, 56 | posets_catalog, 57 | crystals_catalog, 58 | codes_catalog, 59 | matroids_catalog, 60 | games_catalog, 61 | words_catalog, 62 | ] 63 | -------------------------------------------------------------------------------- /tests/test_ExplorerTitle.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sage.misc.sphinxify import sphinxify\n", 10 | "#from sagenb.misc.sphinxify import sphinxify\n", 11 | "s = \"ok\" #Partition.__doc__\n", 12 | "sphinxify(s)" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from ipywidgets import *\n", 22 | "class ExplorerTitleLoc(Box):\n", 23 | " def __init__(self, obj):\n", 24 | " s = \"Exploring: %s\" % obj._repr_()\n", 25 | " s = \"

\" + sphinxify(s) + \"

\"\n", 26 | " l = Layout(width='100%', border='1px solid red', padding='12px', margin='0')\n", 27 | " super(ExplorerTitleLoc, self).__init__()\n", 28 | " self.value = obj\n", 29 | " self.children = (HTMLMath(s, layout=l),)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "t = StandardTableaux(5).random_element()\n", 39 | "#ExplorerTitleLoc(t)\n", 40 | "sphinxify(str(t))" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "s = t._repr_()\n", 50 | "l = Layout(width='100%', border='1px solid red')\n", 51 | "HTMLMath(layout=l)" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "css_lines = []\n", 61 | "css_lines.append(\".explorer-title {background-color: teal; font-size: 150%}\")\n", 62 | "HTML(\"\" % '\\n'.join(css_lines))\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "HTML('
OK
')" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "from new_sage_explorer.new_sage_explorer import ExplorerTitle\n", 81 | "ExplorerTitle(s)" 82 | ] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "SageMath 8.6", 88 | "language": "", 89 | "name": "sagemath" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.6.6" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 2 106 | } 107 | -------------------------------------------------------------------------------- /sage_explorer/properties.yml: -------------------------------------------------------------------------------- 1 | properties: 2 | 3 | ############################################################################## 4 | # Methods for categories 5 | 6 | - property: an_instance 7 | instance of: type # For category classes, and possibly other categories 8 | label: A [typical] instance 9 | 10 | - property: example 11 | instance of: Category 12 | 13 | - property: super_categories 14 | instance of: Category 15 | 16 | - property: axioms 17 | instance of: Category 18 | 19 | - property: structure 20 | instance of: Category 21 | 22 | ############################################################################## 23 | # Methods for parents 24 | 25 | # - property: axioms and structure of the category? 26 | 27 | - property: base_ring 28 | when: has_base 29 | 30 | - property: category 31 | member of: Sets 32 | 33 | - property: an_element 34 | member of: Sets 35 | 36 | - property: cardinality 37 | member of: EnumeratedSets.Finite 38 | 39 | - property: addition_table 40 | member of: sage.categories.additive_semigroups.AdditiveSemigroups 41 | when: cardinality < 25 42 | 43 | - property: multiplication_table 44 | member of: Semigroups.Finite 45 | when: cardinality < 25 46 | 47 | - property: characteristic 48 | member of: Fields 49 | 50 | - property: polynomial 51 | member of: Fields.Finite 52 | 53 | - property: genus 54 | instance of: sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic 55 | 56 | - property: cremona_label 57 | instance of: sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic 58 | 59 | - property: a_invariants 60 | instance of: sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic 61 | 62 | - property: b_invariants 63 | instance of: sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic 64 | 65 | - property: conductor 66 | instance of: sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic 67 | 68 | ############################################################################## 69 | # Methods for elements 70 | 71 | # - property: category of the parent? 72 | 73 | - property: parent 74 | instance of: sage.structure.element.Element 75 | label: Element of 76 | 77 | - property: conjugate 78 | member of: Partitions() 79 | 80 | - property: hook_lengths 81 | member of: Partitions() 82 | 83 | - property: evaluation 84 | member of: Tableaux() 85 | not when: is_standard 86 | 87 | - property: is_standard 88 | member of: Tableaux() 89 | not member of: StandardTableaux() 90 | 91 | - property: conjugate 92 | member of: Tableaux() 93 | 94 | - property: charge 95 | member of: Tableaux() 96 | 97 | - property: cocharge 98 | member of: Tableaux() 99 | 100 | ############################################################################## 101 | # Methods for homsets 102 | # - property: an_element (probably already derived from Parent) 103 | # - property: the category this is a morphism for 104 | 105 | ############################################################################## 106 | # Methods for morphisms 107 | # - property: domain, codomain 108 | # - property: the category this is a morphism for 109 | -------------------------------------------------------------------------------- /docs/diagram/sage-explorer-interactivity.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,11pt, svgnames]{article} 2 | 3 | \usepackage{geometry} 4 | \geometry{a4paper, margin=.5in} 5 | 6 | \usepackage{tikz-uml} 7 | %\usetikzlibrary{positioning} 8 | 9 | \begin{document} 10 | 11 | %\begin{center} 12 | \begin{tikzpicture} 13 | 14 | \umlclass[x=-1.5,y=0.5]{ExplorerTitle}{ 15 | value: object \\ 16 | content: string 17 | }{} 18 | \umlclass[x=-1.5,y=-2]{ExplorerDescription}{ 19 | value: object \\ 20 | content: string 21 | }{} 22 | 23 | 24 | \umlclass[x=0,y=-5,fill=purple!10,anchor=east]{ExplorerHistory}{ 25 | value: object \\ 26 | \_history: ExplorableHistory \\ 27 | \_history\_len: int \\ 28 | \_history\_index: int \\ 29 | }{} 30 | \umlclass[x=0,y=-7.8,fill=gray!10,anchor=east]{ExplorableHistory(deque)}{ 31 | initial\_name: string 32 | }{} 33 | 34 | \umlclass[x=-1,y=-12,fill=orange!20,anchor=east]{ExplorerOutput}{ 35 | value: object \\ 36 | }{} 37 | \umlclass[x=-1,y=-14.2,anchor=east]{ExplorableCell1}{ 38 | explorable: object \\ 39 | new\_val: object \\ 40 | }{} 41 | \umlclass[x=-1,y=-16.5,anchor=east]{ExplorableValue1}{ 42 | explorable: object \\ 43 | new\_val: object \\ 44 | }{} 45 | 46 | \umlclass[x=6,y=1.5,fill=orange!20,anchor=east]{ExplorerProperties}{ 47 | value: object \\ 48 | 49 | }{} 50 | \umlclass[x=6,y=-1,anchor=east]{ExplorableCell}{ 51 | explorable: object \\ 52 | new\_val: object \\ 53 | }{} 54 | \umlclass[x=6,y=-3.4,anchor=east]{ExplorableValue}{ 55 | explorable: object \\ 56 | new\_val: object \\ 57 | }{} 58 | 59 | \umlclass[x=3.9,y=-8,fill=blue!15]{SageExplorer}{ 60 | value: object \\ 61 | \_history: ExplorableHistory \\ 62 | \_history\_len: int \\ 63 | \_history\_index: int 64 | }{} 65 | 66 | \umlclass[x=3,y=-14]{ExplorerHelp}{ 67 | value: object \\ 68 | content: string \\ 69 | explored: ExploredMember 70 | }{} 71 | 72 | \umlclass[x=2.4,y=-17,fill=green!15]{ExplorerCodeCell}{ 73 | value: object \\ 74 | content: string \\ 75 | new\_val: object 76 | }{} 77 | 78 | \umlclass[x=9,y=0,fill=orange!20]{ExplorerVisual}{ 79 | value: object \\ 80 | new\_val: object \\ 81 | 82 | }{} 83 | 84 | \umlclass[x=9,y=-5.4]{ExplorerMethodSearch}{ 85 | value: object \\ 86 | explored: ExploredMember 87 | }{} 88 | 89 | \umlclass[x=9,y=-11]{ExplorerArgs}{ 90 | value: object \\ 91 | content: string \\ 92 | explored: ExploredMember 93 | %no\_args: bool \\ 94 | }{} 95 | 96 | %\umluniassoc [ geometry =−|−, a r g1=x , 97 | %mult 1=1 , pos 1=1.9 , a r g2=y , mult 2=∗ , 98 | %pos 2=0.2 ] {ExplorerMethodSearch}{ExplorerArgs} 99 | \umluniassoc[geometry=-|-,arm1=0.2cm,anchor1=-9,anchor2=-14]{ExplorerMethodSearch}{ExplorerArgs} 100 | \umluniassoc[geometry=|-,anchor1=-32,anchor2=185,arg2=?]{ExplorerDescription}{ExplorerHelp} 101 | \umluniassoc[geometry=|-,anchor1=-145,anchor2=-5,arg2=?]{ExplorerMethodSearch}{ExplorerHelp} 102 | 103 | \umluniassoc[pos=0.6,recursive=178|192|1.5cm,arg2=click]{ExplorableValue}{ExplorableValue} 104 | \umluniassoc[geometry=-|-,arm1=-1cm,arm2=0.1cm,weight=0.1,anchor1=192,anchor2=194]{ExplorableValue}{ExplorableCell} 105 | \umluniassoc[geometry=-|-,arm1=-1cm,arm2=0.1cm,weight=0.3,anchor1=190,anchor2=185]{ExplorableCell}{ExplorerProperties} 106 | \umluniassoc[geometry=-|-,arm1=-3cm,arm2=0.1cm,weight=0.6]{ExplorerProperties}{SageExplorer} 107 | 108 | \umluniassoc[pos=0.6,recursive=178|192|1.5cm,arg2=click]{ExplorableValue1}{ExplorableValue1} 109 | \umluniassoc[geometry=-|-,arm1=-1cm,arm2=0.1cm,weight=0.1,anchor1=192,anchor2=194]{ExplorableValue1}{ExplorableCell1} 110 | \umluniassoc[geometry=-|-,arm1=-1cm,arm2=0.1cm,weight=0.3,anchor1=190,anchor2=185]{ExplorableCell1}{ExplorerOutput} 111 | \umlassoc[geometry=-|-,arm1=0.4cm,anchor1=-20,anchor2=199]{ExplorerHistory}{SageExplorer} 112 | \umluniassoc[geometry=-|-,anchor1=190,anchor2=-10]{SageExplorer}{ExplorerHistory} 113 | \umluniassoc[geometry=-|,anchor1=-4]{ExplorerOutput}{SageExplorer} 114 | \umluniassoc{ExplorerVisual}{SageExplorer} 115 | \umluniassoc[pos=0.6,recursive=-20|11|1cm]{SageExplorer}{SageExplorer} 116 | \umluniassoc[pos=0.6,recursive=198|170|1cm]{ExplorerHistory}{ExplorerHistory} 117 | 118 | \end{tikzpicture} 119 | %\end{center} 120 | 121 | \vspace{1cm} 122 | 123 | 124 | %\begin{tikzpicture} 125 | 126 | % \umlclass{ExplorableHistory(deque)}{ 127 | % length: int \\ 128 | % current\_index: int \\ 129 | % initial\_name: string 130 | % }{ 131 | % push(object) \\ 132 | % pop(int=1) \\ 133 | % truncate() 134 | % } 135 | 136 | % \umlclass[x=7, y=0, name=ExplorableValue]{ExplorableValue}{ 137 | % explorable: object \\ 138 | % new\_val: object 139 | % }{} 140 | 141 | % \umluniassoc{ExplorableValue}{ExplorableValue} 142 | 143 | %\end{tikzpicture} 144 | 145 | \end{document} 146 | -------------------------------------------------------------------------------- /demo_sage_explorer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Sage Explorer/browser demo\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Calling `explore(o)` on some Sage object `o` provides a rich\n", 15 | "visualization of this object, displaying the object, together\n", 16 | "with some of its basic properties, a list of the available methods,\n", 17 | "its help, etc. In order to explore related objects, one can click\n", 18 | "on displayed objects and execute methods.\n", 19 | "\n", 20 | "Let's try with a few objects:" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "from sage_explorer import explore" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": { 36 | "scrolled": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "p = Partition([3,3,2,1])\n", 41 | "explore(p)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": { 48 | "scrolled": false 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "t = StandardTableaux(15).random_element()\n", 53 | "explore(t)" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "explore(p.cell_poset())" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "explore(graphs.PetersenGraph())" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "explore(GF(9, name='x'))" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "explore(Crystals().example())" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "## Exploring Sage as a whole" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": { 103 | "scrolled": true 104 | }, 105 | "outputs": [], 106 | "source": [ 107 | "explore()" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "## The explorer as a building block\n", 115 | "Because the explorer is built using Jupyter widgets, it can be used as\n", 116 | "a building block for other applications. To start with, one can change\n", 117 | "the displayed object:" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "widget = explore(p)\n", 127 | "widget" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "widget.set_value(Partition([5,2,1]))" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "### Example: building a dedicated elliptic curve browser\n", 144 | "This provides a browser over the 20 first elliptic curves from Cremona database:" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": { 151 | "scrolled": false 152 | }, 153 | "outputs": [], 154 | "source": [ 155 | "from ipywidgets import Dropdown, VBox\n", 156 | "from IPython.display import display\n", 157 | "\n", 158 | "curves = [E.label() for E in cremona_curves(range(20))]\n", 159 | "explorer = explore(EllipticCurve(curves[0]))\n", 160 | "menu = Dropdown(options=curves, description = \"Curve\")\n", 161 | "def handler(event):\n", 162 | " explorer.set_value(EllipticCurve(event['new']))\n", 163 | "menu.observe(handler, names=['value'])\n", 164 | "display(menu, explorer)" 165 | ] 166 | } 167 | ], 168 | "metadata": { 169 | "kernelspec": { 170 | "display_name": "SageMath 9.0", 171 | "language": "sage", 172 | "name": "sagemath" 173 | }, 174 | "language_info": { 175 | "codemirror_mode": { 176 | "name": "ipython", 177 | "version": 3 178 | }, 179 | "file_extension": ".py", 180 | "mimetype": "text/x-python", 181 | "name": "python", 182 | "nbconvert_exporter": "python", 183 | "pygments_lexer": "ipython3", 184 | "version": "3.7.3" 185 | } 186 | }, 187 | "nbformat": 4, 188 | "nbformat_minor": 2 189 | } 190 | -------------------------------------------------------------------------------- /tests/Mock-Up of sage-explorer with ipyvuetify.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import ipywidgets\n", 10 | "import ipyvuetify as v" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "application/vnd.jupyter.widget-view+json": { 21 | "model_id": "207665a821324e48a11e506d8381089b", 22 | "version_major": 2, 23 | "version_minor": 0 24 | }, 25 | "text/plain": [ 26 | "Layout(children=[Toolbar(children=[ToolbarTitle(children=['Exploring: (object)']), Spacer(), Menu(children=[Bt…" 27 | ] 28 | }, 29 | "metadata": {}, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "def top ( more_children=[], list_style=0 ):\n", 35 | " list_styles = [\n", 36 | " v.List(two_line=True,children=[\n", 37 | " v.ListTile(children=[\n", 38 | " v.ListTileAvatar(children=[v.Icon(children=['zoom_in'])]),\n", 39 | " v.ListTileTitle(children=['Property Key '+str(i+1)]),\n", 40 | " v.ListTileSubTitle(children=['Property Value '+str(i+1)])\n", 41 | " ])\n", 42 | " for i in [1,2,3]\n", 43 | " ]),\n", 44 | " v.Container(grid_list_xl=True,children=[\n", 45 | " v.Layout(row=True,wrap=True,children=[\n", 46 | " v.Flex(xs3=True,children=[v.Chip(children=['Property %d:'%(i+1)])]),\n", 47 | " v.Flex(xs6=True,children=['Value here for property '+str(i+1)]),\n", 48 | " v.Btn(icon=True,children=[v.Icon(children=['zoom_in'])]),\n", 49 | " ])\n", 50 | " for i in [1,2,3]\n", 51 | " ])\n", 52 | " ]\n", 53 | " return v.Layout(column=True,children=[\n", 54 | " v.Toolbar(class_='teal',children=[\n", 55 | " v.ToolbarTitle(children=['Exploring: (object)']),\n", 56 | " v.Spacer(),\n", 57 | " v.Menu(offset_y=True,children=[\n", 58 | " v.Btn(slot='activator',icon=True,children=[v.Icon(children=['settings'])]),\n", 59 | " v.List(children=[\n", 60 | " v.ListTile(children=[v.ListTileTitle(children=['one'])]),\n", 61 | " v.ListTile(children=[v.ListTileTitle(children=['two'])]),\n", 62 | " v.ListTile(children=[v.ListTileTitle(children=['three'])]),\n", 63 | " ])\n", 64 | " ])\n", 65 | " ]),\n", 66 | " v.Container(grid_list_xl=True,children=[\n", 67 | " v.Layout(row=True,wrap=True,children=[\n", 68 | " v.Flex(xs9=True,children=[\n", 69 | " list_styles[list_style]\n", 70 | " ]),\n", 71 | " v.Flex(xs3=True,children=[\n", 72 | " v.Img(src='https://dummyimage.com/150x150/eee/000.png&text=Object+visualization+here')\n", 73 | " ])\n", 74 | " ])\n", 75 | " ])\n", 76 | " ] + more_children )\n", 77 | "top(list_style=1)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 3, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "func_runner = v.Select(outline=True,label='Functions related to (object)',items=[\n", 87 | "# v.ListTile(children=[\n", 88 | "# v.ListTileAvatar(children=[v.Icon(children=['play_arrow'])]),\n", 89 | "# v.ListTileTitle(children=['Function name '+str(i+1)]),\n", 90 | "# v.ListTileSubTitle(children=['Brief help information for function '+str(i+1)])\n", 91 | "# ])\n", 92 | " f'Function name {i+1} (with some brief help about it)'\n", 93 | " for i in [1,2,3]\n", 94 | "])" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 4, 100 | "metadata": { 101 | "scrolled": false 102 | }, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "application/vnd.jupyter.widget-view+json": { 107 | "model_id": "62d2edcb82c6402ab974c3e5eb4d3c3e", 108 | "version_major": 2, 109 | "version_minor": 0 110 | }, 111 | "text/plain": [ 112 | "Layout(children=[Toolbar(children=[ToolbarTitle(children=['Exploring: (object)']), Spacer(), Menu(children=[Bt…" 113 | ] 114 | }, 115 | "metadata": {}, 116 | "output_type": "display_data" 117 | } 118 | ], 119 | "source": [ 120 | "top([\n", 121 | " v.Layout(row=True,children=[\n", 122 | " v.Flex(xs12=True,children=[\n", 123 | " func_runner\n", 124 | " ])\n", 125 | " ])\n", 126 | "])" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "data": { 136 | "text/plain": [ 137 | "'In[5]'" 138 | ] 139 | }, 140 | "execution_count": 5, 141 | "metadata": {}, 142 | "output_type": "execute_result" 143 | } 144 | ], 145 | "source": [ 146 | "In[5]" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 6, 152 | "metadata": {}, 153 | "outputs": [ 154 | { 155 | "data": { 156 | "text/plain": [ 157 | "'top([\\n v.Layout(row=True,children=[\\n v.Flex(xs12=True,children=[\\n func_runner\\n ])\\n ])\\n])'" 158 | ] 159 | }, 160 | "execution_count": 6, 161 | "metadata": {}, 162 | "output_type": "execute_result" 163 | } 164 | ], 165 | "source": [ 166 | "In[4]" 167 | ] 168 | } 169 | ], 170 | "metadata": { 171 | "kernelspec": { 172 | "display_name": "Python 3", 173 | "language": "python", 174 | "name": "python3" 175 | }, 176 | "language_info": { 177 | "codemirror_mode": { 178 | "name": "ipython", 179 | "version": 3 180 | }, 181 | "file_extension": ".py", 182 | "mimetype": "text/x-python", 183 | "name": "python", 184 | "nbconvert_exporter": "python", 185 | "pygments_lexer": "ipython3", 186 | "version": "3.6.6" 187 | } 188 | }, 189 | "nbformat": 4, 190 | "nbformat_minor": 2 191 | } 192 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/slabbe.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/slabbe.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/slabbe" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/slabbe" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /tests/.ipynb_checkpoints/Mock-Up of sage-explorer with ipyvuetify-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import ipywidgets\n", 10 | "import ipyvuetify as v" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "application/vnd.jupyter.widget-view+json": { 21 | "model_id": "9aedcaa84b3042ceb63fe5b7ee2f32c5", 22 | "version_major": 2, 23 | "version_minor": 0 24 | }, 25 | "text/plain": [ 26 | "Layout(children=[Toolbar(children=[ToolbarTitle(children=['Exploring: (object)']), Spacer(), Menu(children=[Bt…" 27 | ] 28 | }, 29 | "metadata": {}, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "def top ( more_children=[], list_style=0 ):\n", 35 | " list_styles = [\n", 36 | " v.List(two_line=True,children=[\n", 37 | " v.ListTile(children=[\n", 38 | " v.ListTileAvatar(children=[v.Icon(children=['zoom_in'])]),\n", 39 | " v.ListTileTitle(children=['Property Key '+str(i+1)]),\n", 40 | " v.ListTileSubTitle(children=['Property Value '+str(i+1)])\n", 41 | " ])\n", 42 | " for i in [1,2,3]\n", 43 | " ]),\n", 44 | " v.Container(grid_list_xl=True,children=[\n", 45 | " v.Layout(row=True,wrap=True,children=[\n", 46 | " v.Flex(xs3=True,children=[v.Chip(children=['Property %d:'%(i+1)])]),\n", 47 | " v.Flex(xs6=True,children=['Value here for property '+str(i+1)]),\n", 48 | " v.Btn(icon=True,children=[v.Icon(children=['zoom_in'])]),\n", 49 | " ])\n", 50 | " for i in [1,2,3]\n", 51 | " ])\n", 52 | " ]\n", 53 | " return v.Layout(column=True,children=[\n", 54 | " v.Toolbar(class_='teal',children=[\n", 55 | " v.ToolbarTitle(children=['Exploring: (object)']),\n", 56 | " v.Spacer(),\n", 57 | " v.Menu(offset_y=True,children=[\n", 58 | " v.Btn(slot='activator',icon=True,children=[v.Icon(children=['settings'])]),\n", 59 | " v.List(children=[\n", 60 | " v.ListTile(children=[v.ListTileTitle(children=['one'])]),\n", 61 | " v.ListTile(children=[v.ListTileTitle(children=['two'])]),\n", 62 | " v.ListTile(children=[v.ListTileTitle(children=['three'])]),\n", 63 | " ])\n", 64 | " ])\n", 65 | " ]),\n", 66 | " v.Container(grid_list_xl=True,children=[\n", 67 | " v.Layout(row=True,wrap=True,children=[\n", 68 | " v.Flex(xs9=True,children=[\n", 69 | " list_styles[list_style]\n", 70 | " ]),\n", 71 | " v.Flex(xs3=True,children=[\n", 72 | " v.Img(src='https://dummyimage.com/150x150/eee/000.png&text=Object+visualization+here')\n", 73 | " ])\n", 74 | " ])\n", 75 | " ])\n", 76 | " ] + more_children )\n", 77 | "top(list_style=1)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 3, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "func_runner = v.Select(outline=True,label='Functions related to (object)',items=[\n", 87 | "# v.ListTile(children=[\n", 88 | "# v.ListTileAvatar(children=[v.Icon(children=['play_arrow'])]),\n", 89 | "# v.ListTileTitle(children=['Function name '+str(i+1)]),\n", 90 | "# v.ListTileSubTitle(children=['Brief help information for function '+str(i+1)])\n", 91 | "# ])\n", 92 | " f'Function name {i+1} (with some brief help about it)'\n", 93 | " for i in [1,2,3]\n", 94 | "])" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 4, 100 | "metadata": { 101 | "scrolled": false 102 | }, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "application/vnd.jupyter.widget-view+json": { 107 | "model_id": "09b7849286fd4aecac948c32c65109b6", 108 | "version_major": 2, 109 | "version_minor": 0 110 | }, 111 | "text/plain": [ 112 | "Layout(children=[Toolbar(children=[ToolbarTitle(children=['Exploring: (object)']), Spacer(), Menu(children=[Bt…" 113 | ] 114 | }, 115 | "metadata": {}, 116 | "output_type": "display_data" 117 | } 118 | ], 119 | "source": [ 120 | "top([\n", 121 | " v.Layout(row=True,children=[\n", 122 | " v.Flex(xs12=True,children=[\n", 123 | " func_runner\n", 124 | " ])\n", 125 | " ])\n", 126 | "])" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "data": { 136 | "text/plain": [ 137 | "'In[5]'" 138 | ] 139 | }, 140 | "execution_count": 5, 141 | "metadata": {}, 142 | "output_type": "execute_result" 143 | } 144 | ], 145 | "source": [ 146 | "In[5]" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 6, 152 | "metadata": {}, 153 | "outputs": [ 154 | { 155 | "data": { 156 | "text/plain": [ 157 | "'top([\\n v.Layout(row=True,children=[\\n v.Flex(xs12=True,children=[\\n func_runner\\n ])\\n ])\\n])'" 158 | ] 159 | }, 160 | "execution_count": 6, 161 | "metadata": {}, 162 | "output_type": "execute_result" 163 | } 164 | ], 165 | "source": [ 166 | "In[4]" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 7, 172 | "metadata": {}, 173 | "outputs": [ 174 | { 175 | "ename": "KeyError", 176 | "evalue": "3", 177 | "output_type": "error", 178 | "traceback": [ 179 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 180 | "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", 181 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mOut\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 182 | "\u001b[0;31mKeyError\u001b[0m: 3" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "Out[3]" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [] 203 | } 204 | ], 205 | "metadata": { 206 | "kernelspec": { 207 | "display_name": "Python 3", 208 | "language": "python", 209 | "name": "python3" 210 | }, 211 | "language_info": { 212 | "codemirror_mode": { 213 | "name": "ipython", 214 | "version": 3 215 | }, 216 | "file_extension": ".py", 217 | "mimetype": "text/x-python", 218 | "name": "python", 219 | "nbconvert_exporter": "python", 220 | "pygments_lexer": "ipython3", 221 | "version": "3.6.6" 222 | } 223 | }, 224 | "nbformat": 4, 225 | "nbformat_minor": 2 226 | } 227 | -------------------------------------------------------------------------------- /sage_explorer/_widgets.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defining standard widgets for some Sage classes 3 | """ 4 | 5 | import traitlets 6 | from ipywidgets import Box, HTML 7 | from sage.misc.bindable_class import BindableClass 8 | from ipympl.backend_nbagg import Canvas, FigureManager 9 | from matplotlib._pylab_helpers import Gcf 10 | from sage.all import SAGE_TMP, SageObject, plot, plot3d 11 | import sage.all 12 | from os.path import join as path_join 13 | 14 | # Workaround: 15 | # Tableau is lazy imported by default, and lazy import objects don't yet have a 16 | # __setattr__ method (see #25898). This forces a normal import. 17 | 18 | import sage.misc.classcall_metaclass 19 | from six import add_metaclass 20 | class MetaHasTraitsClasscallMetaclass (traitlets.traitlets.MetaHasTraits, sage.misc.classcall_metaclass.ClasscallMetaclass): 21 | pass 22 | @add_metaclass(MetaHasTraitsClasscallMetaclass) 23 | class BindableWidgetClass(BindableClass): 24 | pass 25 | 26 | 27 | def guess_plot(obj, figsize=4): 28 | r""" 29 | Find the corresponding graphics object, if there is one. 30 | 31 | TESTS:: 32 | 33 | sage: from sage_explorer import guess_plot 34 | sage: guess_plot(sin) 35 | Graphics object consisting of 1 graphics primitive 36 | sage: guess_plot(list(cremona_curves(srange(35)))[0]) 37 | Graphics object consisting of 1 graphics primitive 38 | sage: x, y = var('x y') 39 | sage: g(x,y) = x**2 + y**2 40 | sage: guess_plot(g) 41 | Graphics3d Object 42 | sage: type(guess_plot(Partition([4,3]))) 43 | ... 44 | 45 | """ 46 | plt = None 47 | if hasattr(obj, 'number_of_arguments') and obj.number_of_arguments() == 2: 48 | if type(figsize) == type(()): 49 | if len(figsize) == 2: 50 | urange, vrange = figsize 51 | elif len(figsize) == 1: 52 | urange, vrange = figsize, figsize 53 | elif str(figsize).isnumeric(): 54 | urange = vrange = (-float(figsize)/2, float(figsize)/2) 55 | try: 56 | plt = plot3d(obj, urange, vrange) 57 | except: 58 | pass 59 | return plt 60 | try: 61 | plt = plot(obj, figsize=figsize) 62 | except: 63 | try: 64 | plt = obj.plot() 65 | except: 66 | try: 67 | plt = plot(obj) 68 | except: 69 | pass 70 | if plt is not None and plt._objects: 71 | return plt 72 | else: 73 | return None 74 | 75 | 76 | def apply_css(css_line): 77 | try: 78 | ip = get_ipython() 79 | for base in ip.__class__.__mro__: 80 | """If we are in a notebook, we will find 'notebook' in those names""" 81 | if 'otebook' in base.__name__: 82 | ip.display_formatter.format(HTML("" % css_line)) 83 | break 84 | except: 85 | pass # We are in the test environment 86 | 87 | 88 | class MPLWidget(Canvas, BindableWidgetClass): 89 | r"""A widget for plotting interactively. Based on ipympl Canvas.""" 90 | value = traitlets.Instance(SageObject) 91 | 92 | def __init__(self, obj, name=None): 93 | r""" 94 | 95 | TESTS:: 96 | 97 | sage: from sage_explorer import MPLWidget 98 | sage: w = MPLWidget(sin) 99 | sage: w.name 100 | 'sin' 101 | sage: w = MPLWidget(list(cremona_curves(srange(35)))[0]) 102 | sage: w.name 103 | 'Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field' 104 | sage: x, y = var('x y') 105 | sage: g(x,y) = x**2 + y**2 106 | sage: w = MPLWidget(g) 107 | sage: w.name 108 | '(x, y) |--> x^2 + y^2' 109 | """ 110 | 111 | plt = guess_plot(obj) 112 | fig = plt.matplotlib() 113 | fig.set_label(" ") 114 | apply_css(".jupyter-matplotlib-figure > .widget-label { height: 0 }") 115 | Canvas.__init__(self, fig) 116 | self.value = obj 117 | if not name: 118 | name = repr(obj) 119 | self.name = name 120 | manager = FigureManager(self, 1) 121 | def closer(event): 122 | Gcf.destroy(num) 123 | self.mpl_connect('close_event', closer) 124 | 125 | 126 | class PlotWidget(Box, BindableWidgetClass): 127 | r"""A widget for plotting any plottable object""" 128 | value = traitlets.Instance(SageObject) 129 | plot = traitlets.Instance(SageObject) 130 | name = traitlets.Unicode() 131 | 132 | def __init__(self, obj, figsize=4, name=None): 133 | r""" 134 | Which is the specialized widget class name for viewing this object (if any) 135 | 136 | TESTS:: 137 | 138 | sage: from sage_explorer._widgets import PlotWidget 139 | sage: w = PlotWidget(sin) 140 | sage: w.name 141 | 'sin' 142 | sage: len(w.children) 143 | 1 144 | sage: w = PlotWidget(list(cremona_curves(srange(35)))[0]) 145 | sage: w.name 146 | 'Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field' 147 | sage: x, y = var('x y') 148 | sage: g(x,y) = x**2 + y**2 149 | sage: w = PlotWidget(g) 150 | sage: w.name 151 | '(x, y) |--> x^2 + y^2' 152 | """ 153 | super(PlotWidget, self).__init__() 154 | self.value = obj 155 | if not name: 156 | name = repr(obj) 157 | self.name = name 158 | svgfilename = path_join(SAGE_TMP, '%s.svg' % name) 159 | pngfilename = path_join('.', '%s.png' % name) 160 | if hasattr(obj, 'number_of_arguments') and obj.number_of_arguments() == 2: 161 | if type(figsize) == type(()): 162 | if len(figsize) == 2: 163 | urange, vrange = figsize 164 | elif len(figsize) == 1: 165 | urange, vrange = figsize, figsize 166 | elif str(figsize).isnumeric(): 167 | urange = vrange = (-float(figsize)/2, float(figsize)/2) 168 | self.plot = plot3d(obj, urange, vrange) 169 | else: 170 | try: 171 | self.plot = plot(obj, figsize=figsize) 172 | except: 173 | try: 174 | self.plot = obj.plot() 175 | except: 176 | self.plot = plot(obj) 177 | try: 178 | self.plot.save(svgfilename) 179 | self.children = [HTML(open(svgfilename, 'rb').read())] 180 | except: 181 | self.plot.save(pngfilename) 182 | self.children = [HTML('')] 183 | 184 | sage.schemes.curves.curve.Curve_generic._widget_ = PlotWidget 185 | 186 | # Crystals can be slow / infinite to plot; one heuristic would be to plot the 187 | # crystal on its first 10/20 elements. 188 | # For now it would be best to only set this for Crystals.Finite, but it does 189 | # not yet have a ParentMethods 190 | sage.categories.crystals.Crystals.ParentMethods._widget_ = PlotWidget 191 | sage.combinat.posets.poset_examples.Posets().Finite().ParentMethods._widget_ = PlotWidget 192 | sage.graphs.generic_graph.GenericGraph._widget_ = PlotWidget 193 | 194 | # The decision for whether to display the graph or not is duplicating what's 195 | # already done in the Jupyter notebook REPL; this logic should presumably be 196 | # shared. 197 | 198 | 199 | # Additional widgets if sage-combinat-widgets is installed 200 | try: 201 | import sage_combinat_widgets 202 | except: 203 | pass 204 | else: 205 | sage.combinat.tableau.Tableau._widget_ = sage_combinat_widgets.GridViewWidget 206 | sage.combinat.skew_tableau.SkewTableau._widget_ = sage_combinat_widgets.GridViewWidget 207 | sage.combinat.partition.Partition._widget_ = sage_combinat_widgets.grid_view_widget.PartitionGridViewWidget 208 | sage.combinat.skew_partition.SkewPartition._widget_ = sage_combinat_widgets.grid_view_widget.PartitionGridViewWidget 209 | #sage.graphs.graph.Graph._widget_ = sage_combinat_widgets.GridViewWidget # FIXME only GridGraph and AztecDiamondGraph 210 | #sage.graphs.AztecDiamondGraph._widget_ = sage_combinat_widgets.GridViewWidget 211 | sage.matrix.matrix2._widget_ = sage_combinat_widgets.GridViewWidget 212 | -------------------------------------------------------------------------------- /tests/test_python3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sage_explorer import explore\n", 10 | "w = explore(42)\n", 11 | "w" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 6, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/plain": [ 22 | "{'__class__': ,\n", 23 | " '__contains__': ,\n", 24 | " '__delattr__': ,\n", 25 | " '__dict__': ,\n", 26 | " '__dir__': ,\n", 27 | " '__doc__': ,\n", 28 | " '__eq__': ,\n", 29 | " '__format__': ,\n", 30 | " '__ge__': ,\n", 31 | " '__getattribute__': ,\n", 32 | " '__getitem__': ,\n", 33 | " '__gt__': ,\n", 34 | " '__hash__': ,\n", 35 | " '__init__': ,\n", 36 | " '__init_subclass__': ,\n", 37 | " '__iter__': ,\n", 38 | " '__le__': ,\n", 39 | " '__len__': ,\n", 40 | " '__lt__': ,\n", 41 | " '__module__': ,\n", 42 | " '__ne__': ,\n", 43 | " '__new__': ,\n", 44 | " '__reduce__': ,\n", 45 | " '__reduce_ex__': ,\n", 46 | " '__repr__': ,\n", 47 | " '__setattr__': ,\n", 48 | " '__sizeof__': ,\n", 49 | " '__str__': ,\n", 50 | " '__subclasshook__': ,\n", 51 | " 'add_cycle': ,\n", 52 | " 'add_edge': ,\n", 53 | " 'add_edges_from': ,\n", 54 | " 'add_node': ,\n", 55 | " 'add_nodes_from': ,\n", 56 | " 'add_path': ,\n", 57 | " 'add_star': ,\n", 58 | " 'add_weighted_edges_from': ,\n", 59 | " 'adj': ,\n", 60 | " 'adjacency': ,\n", 61 | " 'adjlist_inner_dict_factory': ,\n", 62 | " 'adjlist_outer_dict_factory': ,\n", 63 | " 'clear': ,\n", 64 | " 'copy': ,\n", 65 | " 'degree': ,\n", 66 | " 'edge_attr_dict_factory': ,\n", 67 | " 'edge_subgraph': ,\n", 68 | " 'edges': ,\n", 69 | " 'fresh_copy': ,\n", 70 | " 'get_edge_data': ,\n", 71 | " 'has_edge': ,\n", 72 | " 'has_node': ,\n", 73 | " 'has_predecessor': ,\n", 74 | " 'has_successor': ,\n", 75 | " 'in_degree': ,\n", 76 | " 'in_edges': ,\n", 77 | " 'is_directed': ,\n", 78 | " 'is_multigraph': ,\n", 79 | " 'name': ,\n", 80 | " 'nbunch_iter': ,\n", 81 | " 'neighbors': ,\n", 82 | " 'node': ,\n", 83 | " 'node_dict_factory': ,\n", 84 | " 'nodes': ,\n", 85 | " 'nodes_with_selfloops': ,\n", 86 | " 'number_of_edges': ,\n", 87 | " 'number_of_nodes': ,\n", 88 | " 'number_of_selfloops': ,\n", 89 | " 'order': ,\n", 90 | " 'out_degree': ,\n", 91 | " 'out_edges': ,\n", 92 | " 'pred': ,\n", 93 | " 'predecessors': ,\n", 94 | " 'remove_edge': ,\n", 95 | " 'remove_edges_from': ,\n", 96 | " 'remove_node': ,\n", 97 | " 'remove_nodes_from': ,\n", 98 | " 'reverse': ,\n", 99 | " 'selfloop_edges': ,\n", 100 | " 'size': ,\n", 101 | " 'subgraph': ,\n", 102 | " 'succ': ,\n", 103 | " 'successors': ,\n", 104 | " 'to_directed': ,\n", 105 | " 'to_directed_class': ,\n", 106 | " 'to_undirected': ,\n", 107 | " 'to_undirected_class': ,\n", 108 | " 'update': }" 109 | ] 110 | }, 111 | "execution_count": 6, 112 | "metadata": {}, 113 | "output_type": "execute_result" 114 | } 115 | ], 116 | "source": [ 117 | "w.searchbox.members_dict" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "#import sage.all\n", 127 | "#from sage.misc.sageinspect import sage_getargspec as getargspec\n", 128 | "#getargspec?" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 5, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "data": { 138 | "application/vnd.jupyter.widget-view+json": { 139 | "model_id": "d1e5a6792df742aab3ffd7ccdb32d86d", 140 | "version_major": 2, 141 | "version_minor": 0 142 | }, 143 | "text/plain": [ 144 | "SageExplorer(children=(VBox(children=(ExplorerTitle(children=(MathTitle(value='Exploring: ', _dom_classes=('ti…" 145 | ] 146 | }, 147 | "metadata": {}, 148 | "output_type": "display_data" 149 | } 150 | ], 151 | "source": [ 152 | "from networkx import DiGraph\n", 153 | "G = DiGraph()\n", 154 | "G.add_nodes_from([1,2,3])\n", 155 | "from sage_explorer import explore\n", 156 | "w = explore(G)\n", 157 | "w" 158 | ] 159 | } 160 | ], 161 | "metadata": { 162 | "kernelspec": { 163 | "display_name": "Python 3", 164 | "language": "python", 165 | "name": "python3" 166 | }, 167 | "language_info": { 168 | "codemirror_mode": { 169 | "name": "ipython", 170 | "version": 3 171 | }, 172 | "file_extension": ".py", 173 | "mimetype": "text/x-python", 174 | "name": "python", 175 | "nbconvert_exporter": "python", 176 | "pygments_lexer": "ipython3", 177 | "version": "3.7.3" 178 | } 179 | }, 180 | "nbformat": 4, 181 | "nbformat_minor": 2 182 | } 183 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # sample documentation build configuration file, 4 | # inspried by slabbe configuration file created sphinx-quickstart 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # General information about the project. 16 | project = u"A `Jupyter Notebook widget for exploring SageMath objects." 17 | copyright = u'2018, Odile Bénassy & Nicolas Thiéry' 18 | package_name = 'sage_explorer' 19 | package_folder = "../../sage_explorer" 20 | authors = u"Odile Bénassy, Nicolas M. Thiéry" 21 | 22 | import sys 23 | if sys.version_info[0] >= 3: 24 | unicode = str 25 | import os 26 | 27 | from sage.env import SAGE_DOC_SRC, SAGE_DOC, SAGE_SRC 28 | 29 | try: 30 | import sage.all 31 | except ImportError: 32 | raise RuntimeError("to build the documentation you need to be inside a Sage shell (run first the command 'sage -sh' in a shell") 33 | 34 | 35 | 36 | # If extensions (or modules to document with autodoc) are in another directory, 37 | # add these directories to sys.path here. If the directory is relative to the 38 | # documentation root, use os.path.abspath to make it absolute, like shown here. 39 | sys.path.append(os.path.abspath(package_folder)) 40 | sys.path.append(os.path.join(SAGE_SRC, "sage_setup", "docbuild", "ext")) 41 | 42 | # -- General configuration ------------------------------------------------ 43 | 44 | # If your documentation needs a minimal Sphinx version, state it here. 45 | #needs_sphinx = '1.0' 46 | 47 | # Add any Sphinx extension module names here, as strings. They can be 48 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 49 | # ones. 50 | extensions = [ 51 | #'sphinx.ext.autodoc', 52 | 'sage_autodoc', 53 | 'sphinx.ext.doctest', 54 | 'sphinx.ext.todo', 55 | 'sphinx.ext.coverage', 56 | 'sphinx.ext.mathjax', 57 | 'sphinx.ext.extlinks', 58 | ] 59 | 60 | # Add any paths that contain templates here, relative to this directory. 61 | # templates_path = ['_templates'] 62 | templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates'), '_templates'] 63 | 64 | # The suffix of source filenames. 65 | source_suffix = '.rst' 66 | 67 | # The encoding of source files. 68 | #source_encoding = 'utf-8-sig' 69 | 70 | # The master toctree document. 71 | master_doc = 'index' 72 | 73 | 74 | 75 | # The version info for the project you're documenting, acts as replacement for 76 | # |version| and |release|, also used in various other places throughout the 77 | # built documents. 78 | # 79 | # The short X.Y version. 80 | version = open("../../VERSION").read().strip() 81 | # The full version, including alpha/beta/rc tags. 82 | release = version 83 | 84 | # The language for content autogenerated by Sphinx. Refer to documentation 85 | # for a list of supported languages. 86 | #language = None 87 | 88 | # There are two options for replacing |today|: either, you set today to some 89 | # non-false value, then it is used: 90 | #today = '' 91 | # Else, today_fmt is used as the format for a strftime call. 92 | #today_fmt = '%B %d, %Y' 93 | 94 | # List of patterns, relative to source directory, that match files and 95 | # directories to ignore when looking for source files. 96 | exclude_patterns = [] 97 | 98 | # The reST default role (used for this markup: `text`) to use for all 99 | # documents. 100 | default_role = 'math' 101 | 102 | # If true, '()' will be appended to :func: etc. cross-reference text. 103 | #add_function_parentheses = True 104 | 105 | # If true, the current module name will be prepended to all description 106 | # unit titles (such as .. function::). 107 | #add_module_names = True 108 | 109 | # If true, sectionauthor and moduleauthor directives will be shown in the 110 | # output. They are ignored by default. 111 | #show_authors = False 112 | 113 | # The name of the Pygments (syntax highlighting) style to use. 114 | pygments_style = 'sphinx' 115 | 116 | # A list of ignored prefixes for module index sorting. 117 | #modindex_common_prefix = [] 118 | 119 | # If true, keep warnings as "system message" paragraphs in the built documents. 120 | #keep_warnings = False 121 | 122 | pythonversion = sys.version.split(' ')[0] 123 | # Python and Sage trac ticket shortcuts. For example, :trac:`7549` . 124 | extlinks = { 125 | 'python': ('https://docs.python.org/release/'+pythonversion+'/%s', ''), 126 | 'trac': ('http://trac.sagemath.org/%s', 'trac ticket #'), 127 | 'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article '), 128 | 'arxiv': ('http://arxiv.org/abs/%s', 'Arxiv '), 129 | 'oeis': ('https://oeis.org/%s', 'OEIS sequence '), 130 | 'doi': ('https://dx.doi.org/%s', 'doi:'), 131 | 'mathscinet': ('http://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet ') 132 | } 133 | 134 | # -- Options for HTML output ---------------------------------------------- 135 | 136 | # The theme to use for HTML and HTML Help pages. See the documentation for 137 | # a list of builtin themes. 138 | html_theme = 'default' 139 | 140 | # Theme options are theme-specific and customize the look and feel of a theme 141 | # further. For a list of options available for each theme, see the 142 | # documentation. 143 | html_theme_options = {} 144 | 145 | 146 | # Add any paths that contain custom themes here, relative to this directory. 147 | #html_theme_path = [] 148 | #html_theme_path = [os.path.join(SAGE_DOC_SRC, 'common', 'themes')] 149 | html_theme_path = [os.path.join(SAGE_DOC_SRC, 'common', 'themes', 'sage')] 150 | 151 | # The name for this set of Sphinx documents. If None, it defaults to 152 | # " v documentation". 153 | 154 | # A shorter title for the navigation bar. Default is the same as html_title. 155 | #html_short_title = None 156 | 157 | # The name of an image file (relative to this directory) to place at the top 158 | # of the sidebar. 159 | #html_logo = None 160 | 161 | # The name of an image file (within the static path) to use as favicon of the 162 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 163 | # pixels large. 164 | #html_favicon = None 165 | 166 | # Add any paths that contain custom static files (such as style sheets) here, 167 | # relative to this directory. They are copied after the builtin static files, 168 | # so a file named "default.css" will overwrite the builtin "default.css". 169 | #html_static_path = ['_static'] 170 | html_static_path = [] 171 | 172 | # Add any extra paths that contain custom files (such as robots.txt or 173 | # .htaccess) here, relative to this directory. These files are copied 174 | # directly to the root of the documentation. 175 | #html_extra_path = [] 176 | 177 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 178 | # using the given strftime format. 179 | #html_last_updated_fmt = '%b %d, %Y' 180 | 181 | # If true, SmartyPants will be used to convert quotes and dashes to 182 | # typographically correct entities. 183 | #html_use_smartypants = True 184 | 185 | # Custom sidebar templates, maps document names to template names. 186 | #html_sidebars = {} 187 | 188 | # Additional templates that should be rendered to pages, maps page names to 189 | # template names. 190 | #html_additional_pages = {} 191 | 192 | # If false, no module index is generated. 193 | #html_domain_indices = True 194 | 195 | # If false, no index is generated. 196 | #html_use_index = True 197 | 198 | # If true, the index is split into individual pages for each letter. 199 | #html_split_index = False 200 | 201 | # If true, links to the reST sources are added to the pages. 202 | #html_show_sourcelink = True 203 | 204 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 205 | #html_show_sphinx = True 206 | 207 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 208 | #html_show_copyright = True 209 | 210 | # If true, an OpenSearch description file will be output, and all pages will 211 | # contain a tag referring to it. The value of this option must be the 212 | # base URL from which the finished HTML is served. 213 | #html_use_opensearch = '' 214 | 215 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 216 | #html_file_suffix = None 217 | 218 | # Output file base name for HTML help builder. 219 | htmlhelp_basename = package_name + "doc" 220 | 221 | 222 | # -- Options for LaTeX output --------------------------------------------- 223 | 224 | latex_elements = { 225 | # The paper size ('letterpaper' or 'a4paper'). 226 | #'papersize': 'letterpaper', 227 | 228 | # The font size ('10pt', '11pt' or '12pt'). 229 | #'pointsize': '10pt', 230 | 231 | # Additional stuff for the LaTeX preamble. 232 | 'preamble': '', 233 | } 234 | 235 | # Grouping the document tree into LaTeX files. List of tuples 236 | # (source start file, target name, title, 237 | # author, documentclass [howto, manual, or own class]). 238 | latex_documents = [ 239 | ('index', package_name + '.tex', u'Documentation of ' + unicode(package_name), 240 | authors, 'manual'), 241 | ] 242 | 243 | # The name of an image file (relative to this directory) to place at the top of 244 | # the title page. 245 | #latex_logo = None 246 | 247 | # For "manual" documents, if this is true, then toplevel headings are parts, 248 | # not chapters. 249 | #latex_use_parts = False 250 | 251 | # If true, show page references after internal links. 252 | #latex_show_pagerefs = False 253 | 254 | # If true, show URL addresses after external links. 255 | #latex_show_urls = False 256 | 257 | # Documents to append as an appendix to all manuals. 258 | #latex_appendices = [] 259 | 260 | # If false, no module index is generated. 261 | #latex_domain_indices = True 262 | 263 | 264 | # -- Options for manual page output --------------------------------------- 265 | 266 | # One entry per manual page. List of tuples 267 | # (source start file, name, description, authors, manual section). 268 | man_pages = [ 269 | ('index', package_name, unicode(package_name) + u" documentation", 270 | [authors], 1) 271 | ] 272 | 273 | # If true, show URL addresses after external links. 274 | #man_show_urls = False 275 | 276 | 277 | # -- Options for Texinfo output ------------------------------------------- 278 | 279 | # Grouping the document tree into Texinfo files. List of tuples 280 | # (source start file, target name, title, author, 281 | # dir menu entry, description, category) 282 | texinfo_documents = [ 283 | ('index', package_name, unicode(package_name) + u" documentation", 284 | authors, package_name, project, 285 | 'Miscellaneous'), 286 | ] 287 | 288 | # Documents to append as an appendix to all manuals. 289 | #texinfo_appendices = [] 290 | 291 | # If false, no module index is generated. 292 | #texinfo_domain_indices = True 293 | 294 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 295 | #texinfo_show_urls = 'footnote' 296 | 297 | # If true, do not generate a @detailmenu in the "Top" node's menu. 298 | #texinfo_no_detailmenu = False 299 | 300 | # -- Options copied from Sagemath conf.py file ------------------------------- 301 | 302 | # We use MathJax to build the documentation unless the environment 303 | # variable SAGE_DOC_MATHJAX is set to "no" or "False". (Note that if 304 | # the user does not set this variable, then the script sage-env sets 305 | # it to "True".) 306 | 307 | if (os.environ.get('SAGE_DOC_MATHJAX', 'no') != 'no' 308 | and os.environ.get('SAGE_DOC_MATHJAX', 'no') != 'False'): 309 | 310 | extensions.append('sphinx.ext.mathjax') 311 | mathjax_path = 'MathJax.js?config=TeX-AMS_HTML-full,../mathjax_sage.js' 312 | 313 | from sage.misc.latex_macros import sage_mathjax_macros 314 | # this is broken for now 315 | # html_theme_options['mathjax_macros'] = sage_mathjax_macros() 316 | 317 | from pkg_resources import Requirement, working_set 318 | sagenb_path = working_set.find(Requirement.parse('sagenb')).location 319 | mathjax_relative = os.path.join('sagenb','data','mathjax') 320 | 321 | # It would be really nice if sphinx would copy the entire mathjax directory, 322 | # (so we could have a _static/mathjax directory), rather than the contents of the directory 323 | 324 | mathjax_static = os.path.join(sagenb_path, mathjax_relative) 325 | html_static_path.append(mathjax_static) 326 | exclude_patterns=['**/'+os.path.join(mathjax_relative, i) for i in ('docs', 'README*', 'test', 327 | 'unpacked', 'LICENSE')] 328 | else: 329 | extensions.append('sphinx.ext.pngmath') 330 | 331 | # This is to make the verbatim font smaller; 332 | # Verbatim environment is not breaking long lines 333 | from sphinx.highlighting import PygmentsBridge 334 | from pygments.formatters.latex import LatexFormatter 335 | 336 | class CustomLatexFormatter(LatexFormatter): 337 | def __init__(self, **options): 338 | super(CustomLatexFormatter, self).__init__(**options) 339 | self.verboptions = r"formatcom=\footnotesize" 340 | 341 | PygmentsBridge.latex_formatter = CustomLatexFormatter 342 | 343 | latex_elements['preamble'] += r''' 344 | % One-column index 345 | \makeatletter 346 | \renewenvironment{theindex}{ 347 | \chapter*{\indexname} 348 | \markboth{\MakeUppercase\indexname}{\MakeUppercase\indexname} 349 | \setlength{\parskip}{0.1em} 350 | \relax 351 | \let\item\@idxitem 352 | }{} 353 | \makeatother 354 | \renewcommand{\ttdefault}{txtt} 355 | ''' 356 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /sage_explorer/explored_member.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | Sage Explorer in Jupyter Notebook 4 | 5 | EXAMPLES :: 6 | from sage.combinat.tableau import StandardTableaux 7 | from SageExplorer import * 8 | t = StandardTableaux(15).random_element() 9 | widget = SageExplorer(t) 10 | display(t) 11 | 12 | AUTHORS: 13 | - Odile Bénassy, Nicolas Thiéry 14 | 15 | """ 16 | import os, re, six, types, operator as OP 17 | from inspect import getargspec, getmro, isclass, isabstract 18 | #from functools import lru_cache 19 | try: # Are we in a Sage environment? 20 | import sage.all 21 | from sage.misc.sageinspect import sage_getargspec as getargspec 22 | from sage.misc.sage_eval import sage_eval as eval 23 | except: 24 | pass 25 | 26 | EXCLUDED_MEMBERS = ['__init__', '__repr__', '__str__'] 27 | OPERATORS = {'==' : OP.eq, '<' : OP.lt, '<=' : OP.le, '>' : OP.gt, '>=' : OP.ge} 28 | 29 | import __main__ 30 | def _eval_in_main(s, locals={}): 31 | """ 32 | Evaluate the expression `s` in the global scope 33 | 34 | TESTS:: 35 | 36 | sage: from sage_explorer.explored_member import _eval_in_main 37 | sage: from sage.combinat.tableau import Tableaux 38 | sage: _eval_in_main("Tableaux") 39 | 40 | """ 41 | try: 42 | globs = sage.all.__dict__ 43 | except: 44 | globs = {} 45 | globs.update(__main__.__dict__) 46 | globs.update(locals) 47 | return eval(s, globs) 48 | 49 | def getmembers(object): 50 | """Return all members of an object as (name, value) pairs sorted by name. 51 | This function patches inspect.getmembers 52 | because of Python3's new `__weakref__` attribute. 53 | 54 | TESTS:: 55 | 56 | sage: from sage_explorer.sage_explorer import Settings 57 | sage: from sage_explorer.explored_member import getmembers 58 | sage: len(getmembers(1)) 59 | 272 60 | sage: len(getmembers(ZZ)) 61 | 316 62 | """ 63 | if isclass(object): 64 | mro = (object,) + getmro(object) 65 | else: 66 | mro = () 67 | results = [] 68 | processed = set() 69 | names = dir(object) 70 | # :dd any DynamicClassAttributes to the list of names if object is a class; 71 | # this may result in duplicate entries if, for example, a virtual 72 | # attribute with the same name as a DynamicClassAttribute exists 73 | try: 74 | for base in object.__bases__: 75 | for k, v in base.__dict__.items(): 76 | if isinstance(v, types.DynamicClassAttribute): 77 | names.append(k) 78 | except AttributeError: 79 | pass 80 | for key in names: 81 | # First try to get the value via getattr. Some descriptors don't 82 | # like calling their __get__ (see bug #1785), so fall back to 83 | # looking in the __dict__. 84 | if key == '__weakref__': 85 | continue 86 | try: 87 | value = getattr(object, key) 88 | # handle the duplicate key 89 | if key in processed: 90 | raise AttributeError 91 | except AttributeError: 92 | for base in mro: 93 | if key in base.__dict__: 94 | value = base.__dict__[key] 95 | break 96 | else: 97 | # could be a (currently) missing slot member, or a buggy 98 | # __dir__; discard and move on 99 | continue 100 | results.append((key, value)) 101 | processed.add(key) 102 | results.sort(key=lambda pair: pair[0]) 103 | return results 104 | 105 | 106 | class ExploredMember(object): 107 | r""" 108 | A member of an explored object: method, attribute .. 109 | """ 110 | vocabulary = ['name', 'member', 'container', 'member_type', 'doc', 'origin', 'overrides', 'privacy', 'prop_label', 'args', 'defaults'] 111 | 112 | def __init__(self, name, **kws): 113 | r""" 114 | A method or attribute. 115 | Must have a name. 116 | 117 | TESTS:: 118 | 119 | sage: from sage_explorer.explored_member import ExploredMember 120 | sage: from sage.combinat.partition import Partition 121 | sage: p = Partition([3,3,2,1]) 122 | sage: m = ExploredMember('conjugate', container=p) 123 | sage: m.name 124 | 'conjugate' 125 | """ 126 | self.name = name 127 | for arg in kws: 128 | try: 129 | assert arg in self.vocabulary 130 | except: 131 | raise ValueError("Argument '%s' not in vocabulary." % arg) 132 | setattr(self, arg, kws[arg]) 133 | 134 | def compute_member(self, container=None): 135 | r""" 136 | Get method or attribute value, given the name. 137 | 138 | TESTS:: 139 | 140 | sage: from sage_explorer.explored_member import ExploredMember 141 | sage: from sage.combinat.partition import Partition 142 | sage: p = Partition([3,3,2,1]) 143 | sage: m = ExploredMember('conjugate', container=p) 144 | sage: m.compute_member() 145 | sage: m.member 146 | 147 | sage: x = 42 148 | sage: m = ExploredMember('denominator', container=x) 149 | sage: m.compute_member() 150 | sage: str(m.member)[:20] 151 | '", str(type(self.member))) 204 | if m and ('method' in m.group(2)): 205 | self.member_type = m.group(2) 206 | elif callable(self.member): 207 | self.member_type = "callable (%s)" % str(type(self.member)) 208 | else: 209 | self.member_type = "attribute (%s)" % str(type(self.member)) 210 | 211 | def compute_privacy(self): 212 | r""" 213 | Compute member privacy, if any. 214 | 215 | TESTS:: 216 | 217 | sage: from sage_explorer.explored_member import ExploredMember 218 | sage: from sage.combinat.partition import Partition 219 | sage: p = Partition([3,3,2,1]) 220 | sage: m = ExploredMember('__class__', container=p) 221 | sage: m.compute_privacy() 222 | sage: m.privacy 223 | 'python_special' 224 | sage: m = ExploredMember('_doccls', container=p) 225 | sage: m.compute_privacy() 226 | sage: m.privacy 227 | 'private' 228 | """ 229 | if not self.name.startswith('_'): 230 | self.privacy = None 231 | return 232 | if self.name.startswith('__') and self.name.endswith('__'): 233 | self.privacy = 'python_special' 234 | elif self.name.startswith('_') and self.name.endswith('_'): 235 | self.privacy = 'sage_special' 236 | else: 237 | self.privacy = 'private' 238 | 239 | def compute_origin(self, container=None): 240 | r""" 241 | Determine in which base class 'origin' of class 'container' 242 | this member is actually defined, and also return the list 243 | of overrides if any. 244 | 245 | TESTS:: 246 | 247 | sage: from sage_explorer.explored_member import ExploredMember 248 | sage: from sage.combinat.partition import Partition 249 | sage: p = Partition([3,3,2,1]) 250 | sage: m = ExploredMember('_reduction', container=p) 251 | sage: m.compute_origin() 252 | sage: m.origin, m.overrides 253 | (, 254 | [, 255 | , 256 | , 257 | , 258 | , 259 | ]) 260 | """ 261 | if not container: 262 | if not hasattr(self, 'container'): 263 | raise ValueError("Cannot compute origin without a container.") 264 | container = self.container 265 | self.container = container 266 | if isclass(container): 267 | containerclass = container 268 | else: 269 | containerclass = container.__class__ 270 | self.origin = None 271 | self.overrides = [] 272 | for c in containerclass.__mro__: 273 | if self.name in c.__dict__: 274 | self.overrides.append(c) 275 | if self.origin is None: # and getattr(containerclass, self.name) == getattr(c, self.name): 276 | self.origin = c 277 | if self.overrides: 278 | self.overrides = self.overrides[1:] 279 | 280 | def compute_argspec(self, container=None): 281 | r""" 282 | If this member is a method: compute its args and defaults. 283 | 284 | TESTS:: 285 | 286 | sage: from sage_explorer.explored_member import ExploredMember 287 | sage: from sage.combinat.partition import Partition 288 | sage: p = Partition([3,3,2,1]) 289 | sage: m = ExploredMember('add_cell', container=p) 290 | sage: m.compute_member() 291 | sage: m.compute_argspec() 292 | sage: m.args, m.defaults 293 | (['self', 'i', 'j'], (None,)) 294 | sage: x = 42 295 | sage: m = ExploredMember('denominator', container=x) 296 | sage: m.compute_argspec() 297 | sage: m.args 298 | [] 299 | """ 300 | try: 301 | argspec = getargspec(self.member) 302 | if hasattr(argspec, 'args'): 303 | self.args = argspec.args 304 | if hasattr(argspec, 'defaults'): 305 | self.defaults = argspec.defaults 306 | except: # e.g. pure attribute 307 | self.args, self.defaults = [], [] 308 | 309 | def compute_property_label(self, properties_settings={}): 310 | r""" 311 | Retrieve the property label, if any, from configuration 'properties_settings'. 312 | 313 | TESTS:: 314 | 315 | sage: from sage_explorer.explored_member import ExploredMember 316 | sage: F = GF(7) 317 | sage: m = ExploredMember('polynomial', container=F) 318 | sage: m.compute_property_label({'polynomial': [{'in': Fields.Finite}]}) 319 | sage: m.prop_label 320 | 'Polynomial' 321 | sage: G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]]) 322 | sage: m = ExploredMember('cardinality', container=G) 323 | sage: m.compute_property_label({'cardinality': [{'in': EnumeratedSets.Finite}]}) 324 | sage: m.prop_label 325 | 'Cardinality' 326 | sage: m = ExploredMember('category', container=G) 327 | sage: m.compute_property_label({'category': [{'in': Sets, 'label': 'A Better Category Label'}]}) 328 | sage: m.prop_label 329 | 'A Better Category Label' 330 | sage: m = ExploredMember('__abs__', container=1) 331 | sage: m.compute_property_label({'__abs__': [{'label': 'Absolute value'}]}) 332 | sage: m.prop_label 333 | 'Absolute value' 334 | sage: m.compute_property_label({'__abs__': [{'label': 'Absolute value', 'when': lambda x:False}]}) 335 | sage: m.prop_label 336 | sage: from sage.rings.integer_ring import ZZ 337 | sage: m = ExploredMember('cardinality', container=ZZ) 338 | sage: m.compute_property_label({'cardinality': [{'in': Groups().Finite()}]}) 339 | sage: m.prop_label 340 | 'Cardinality' 341 | """ 342 | self.prop_label = None 343 | if self.name not in properties_settings: 344 | return 345 | if not hasattr(self, 'container'): 346 | raise ValueError("Cannot compute property label without a container.") 347 | contexts = properties_settings[self.name] 348 | for context in contexts: 349 | fullfilled = True 350 | if 'instance of' in context.keys(): 351 | """Test instance of""" 352 | if not isinstance(self.container, context['instance of']): 353 | fullfilled = False 354 | continue 355 | if 'not instance of' in context.keys(): 356 | """Test not instance of""" 357 | if isinstance(self.container, context['not instance of']): 358 | fullfilled = False 359 | continue 360 | if 'member of' in context.keys(): 361 | """Test member of""" 362 | try: 363 | if not self.container in context['member of']: 364 | fullfilled = False 365 | continue 366 | except: 367 | fullfilled = False 368 | continue # The error is : descriptor 'category' of 'sage.structure.parent.Parent' object needs an argument 369 | if 'not member of' in context.keys(): 370 | """Test not member of""" 371 | if self.container in context['not member of']: 372 | fullfilled = False 373 | continue 374 | def prepare_when_context(v): 375 | if v is None: 376 | return [] # Shouldn't happen, though 377 | if isinstance(v, (list,)): 378 | return v 379 | else: 380 | return [v] 381 | if 'when' in context.keys(): 382 | """Test when predicate(s)""" 383 | when = prepare_when_context(context['when']) 384 | for predicate in when: 385 | if not predicate(self.container): 386 | fullfilled = False 387 | continue 388 | if fullfilled: 389 | break # contexts should not overlap 390 | if 'not when' in context.keys(): 391 | """Test not when predicate(s)""" 392 | nwhen = prepare_when_context(context['not when']) 393 | for predicate in nwhen: 394 | if predicate(self.container): 395 | fullfilled = False 396 | continue 397 | if fullfilled: 398 | break # contexts should not overlap 399 | if not fullfilled: 400 | return 401 | if 'label' in context.keys(): 402 | self.prop_label = context['label'] 403 | else: 404 | self.prop_label = ' '.join([x.capitalize() for x in self.name.split('_')]) 405 | 406 | 407 | #@lru_cache(maxsize=100) 408 | def get_members(cls, properties_settings, include_private=False): 409 | r""" 410 | Get all members for a class. 411 | 412 | INPUT: ``cls`` a Sage class. 413 | OUTPUT: List of `Member` named tuples. 414 | 415 | TESTS:: 416 | 417 | sage: from sage_explorer.explored_member import get_members 418 | sage: from sage_explorer.sage_explorer import Settings 419 | sage: from sage.combinat.partition import Partition 420 | sage: mm = get_members(42, Settings.properties) 421 | sage: mm = get_members(int(42), Settings.properties) 422 | sage: mm = get_members(NN, Settings.properties) 423 | sage: mm[0].name, mm[0].origin 424 | ('CartesianProduct', None) 425 | sage: mm = get_members(Partition, Settings.properties, include_private=True) 426 | sage: mm[2].name, mm[2].privacy 427 | ('__class__', 'python_special') 428 | sage: [(mm[i].name, mm[i].origin, mm[i].overrides, mm[i].privacy) for i in range(len(mm)) if mm[i].name == '_unicode_art_'] 429 | [('_unicode_art_', 430 | , 431 | [], 432 | 'sage_special')] 433 | sage: from sage.combinat.tableau import Tableau 434 | sage: mm = get_members(Tableau([[1], [2], [3]]), Settings.properties) 435 | sage: [(mm[i].name, mm[i].container, mm[i].origin, mm[i].prop_label) for i in range(len(mm)) if mm[i].name == 'cocharge'] 436 | [('cocharge', [[1], [2], [3]], , 'Cocharge')] 437 | sage: mm = get_members(Groups(), Settings.properties) 438 | sage: (mm[0].name, mm[0].container, mm[0].origin, mm[0].overrides) 439 | ('Algebras', 440 | Category of groups, 441 | , 442 | []) 443 | """ 444 | members = [] 445 | for name, member in getmembers(cls): 446 | if isabstract(member) or 'deprecated' in str(type(member)).lower(): 447 | continue 448 | m = ExploredMember(name, member=member, container=cls) 449 | m.compute_member_type() 450 | m.compute_origin() 451 | m.compute_privacy() 452 | if not include_private and m.privacy: 453 | continue 454 | m.compute_property_label(properties_settings) 455 | members.append(m) 456 | return members 457 | 458 | #@lru_cache(maxsize=500) 459 | def get_properties(obj, properties_settings={}): 460 | r""" 461 | Get all properties for an object. 462 | 463 | INPUT: ``obj`` a Sage object. 464 | OUTPUT: List of `Member` named tuples. 465 | 466 | TESTS:: 467 | 468 | sage: from sage_explorer.explored_member import get_properties 469 | sage: from sage.combinat.tableau import * 470 | sage: st = StandardTableaux(3).an_element() 471 | sage: sst = SemistandardTableaux(3).an_element() 472 | sage: G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]]) 473 | sage: from sage_explorer.sage_explorer import Settings 474 | sage: pp = get_properties(st, Settings.properties) 475 | sage: [p.name for p in pp] 476 | ['charge', 'cocharge', 'conjugate', 'parent'] 477 | sage: pp[3].name, pp[3].prop_label 478 | ('parent', 'Element of') 479 | sage: pp = get_properties(sst, Settings.properties) 480 | sage: pp[3].name, pp[3].prop_label 481 | ('is_standard', 'Is Standard') 482 | sage: pp = get_properties(G, Settings.properties) 483 | sage: [p.name for p in pp] 484 | ['an_element', 'cardinality', 'category'] 485 | sage: len(pp) 486 | 3 487 | sage: [p.name for p in get_properties(1, Settings.properties)] 488 | ['parent'] 489 | sage: Settings.add_property('__abs__') 490 | sage: [p.name for p in get_properties(1, Settings.properties)] 491 | ['__abs__', 'parent'] 492 | sage: Settings.remove_property('__abs__') 493 | sage: Settings.add_property('__abs__', when=lambda x:False) 494 | sage: [p.name for p in get_properties(1, Settings.properties)] 495 | ['parent'] 496 | """ 497 | try: 498 | members = getmembers(obj) 499 | except: 500 | return [] # Can be a numeric value .. 501 | properties = [] 502 | for name, member in members: 503 | if isabstract(member) or 'deprecated' in str(type(member)).lower(): 504 | continue 505 | m = ExploredMember(name, member=member, container=obj) 506 | m.compute_property_label(properties_settings) 507 | if m.prop_label: 508 | properties.append(m) 509 | return properties 510 | -------------------------------------------------------------------------------- /sage_explorer/sage_explorer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | Sage-Explorer: interactive exploration of SageMath objects in Jupyter 4 | 5 | See :class:`SageExplorer`. 6 | 7 | AUTHORS: 8 | - Odile Bénassy, Nicolas Thiéry 9 | 10 | """ 11 | import re, os, warnings, yaml 12 | from abc import abstractmethod 13 | from cysignals.alarm import alarm, cancel_alarm 14 | from cysignals.signals import AlarmInterrupt 15 | from inspect import isclass, ismodule 16 | from collections import deque 17 | from ipywidgets import Box, Button, CallbackDispatcher, Combobox, Dropdown, GridBox, HBox, HTML, HTMLMath, Label, Layout, Text, Textarea, ToggleButton, VBox 18 | from traitlets import Any, Bool, Dict, HasTraits, Instance, Int, Unicode, dlink, link, observe 19 | try: 20 | from sage.misc.sphinxify import sphinxify 21 | assert sphinxify is not None 22 | except: 23 | sphinxify = str 24 | try: 25 | from sage.repl.rich_output import get_display_manager 26 | DISPLAY_MODE = get_display_manager().preferences.text 27 | except: 28 | DISPLAY_MODE = 'plain' 29 | with warnings.catch_warnings(): 30 | warnings.filterwarnings("ignore", category=DeprecationWarning) 31 | from ipyevents import Event 32 | from .explored_member import ExploredMember, _eval_in_main, get_members, get_properties 33 | import sage_explorer._sage_catalog as sage_catalog 34 | from ._sage_catalog import sage_catalogs 35 | try: 36 | from singleton_widgets import ButtonSingleton, ComboboxSingleton, DropdownSingleton, HTMLMathSingleton, TextSingleton, TextareaSingleton, ToggleButtonSingleton 37 | except: 38 | ButtonSingleton, ComboboxSingleton, DropdownSingleton, HTMLMathSingleton, TextSingleton, TextareaSingleton, ToggleButtonSingleton = Button, Combobox, Dropdown, HTMLMath, Text, Textarea, ToggleButton 39 | 40 | title_layout = Layout(width='100%', padding='12px') 41 | css_lines = [] 42 | css_lines.append(".visible {visibility: visible; display: flex}") 43 | css_lines.append(".invisible {visibility: hidden; display: none}") 44 | css_lines.append(".title-level2 {font-size: 150%}") 45 | css_lines.append(".separator {width: 1em}") 46 | css_lines.append('.explorer-title {background-color: #005858; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfjCBQVGx7629/nAAAB60lEQVQ4y5WUQU8aQRSAv0UPGDcaJYKaCISj6VFJ6skfoEc5EUhIvWniTzFpjyb2ZJB/UBtMf0BvwA0WIfFAemqRSGJ2xwPDMLuz227nXd68ed/uvDfvPYhaZTwEAo9ylEsiEs5iAWCRjQOvs6ntCiEabLIeBqe4pkFR7mwfbEvtgDqf2QreIMVXXARP1MhQosEYIWVMgxJpKjgIPO5I6+gat7jKtcOrAufySos/UveoswGwDMASuyoAm/2Q3CT5oHSLjOTkOsQx/hYlQ46C365oUf5NJpybF9uh5XN6o0uTJl3efPYOuyZcYqq59LkkS5IkWS7oaydTSn7QYpWGDz32nR/78HvsWfVZlNmjQIGiKgWXK74E7nXBNUtSf+EnDg4D1PsupEveCCpPz/BzEyGtMWBk2EYMDFsiuqtirASeYcuRMWwZcobNW6ZqJCzPiZGwUw3WEjZ/qvv/f6rFMoskxwor5P5VJAA7tAPl2aNJk16gPFtsm3CNSazGeKEaRIs8xW5Jh0Md3eAhNioQfJtNklmRuDyr9x7TZmoENaXDROqCEa5+OB+AfSqkOQsZgNt8YigHYMj8vOW7isbmUcGPqnw+8oN6SP0RHPo3Cr7RrFukFht9Cv72fcoJ0eCX7hLdVUOETM8wyuUdTAVXcgNG490AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDgtMjBUMTk6Mjc6MzArMDI6MDCNIxYDAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA4LTIwVDE5OjI3OjMwKzAyOjAw/H6uvwAAAABJRU5ErkJggg=="); background-repeat: no-repeat; background-position: right;background-origin: content-box; border-radius: 4px}') 47 | css_lines.append(".explorer-title DIV {color: seashell; padding-bottom: 2px}") # (light teal=#46C6C6) 48 | css_lines.append(".explorer-table {border-collapse: collapse}") 49 | css_lines.append(".explorer-flexrow {padding:0; display:flex; flex-flow:row wrap; width:99%}") 50 | css_lines.append(".explorer-flexitem {flex-grow:1}") 51 | css_lines.append(".explorable-value {background-color: #eee; border-radius: 4px; padding: 4px}\n.explorable-value:hover {cursor: pointer}") 52 | global_css_code = HTML("" % '\n'.join(css_lines)) 53 | 54 | TIMEOUT = 0.5 # in seconds 55 | MAX_LEN_HISTORY = 50 56 | CONFIG_PROPERTIES = yaml.load(open(os.path.join(os.path.dirname(__file__),'properties.yml')), yaml.SafeLoader) 57 | 58 | 59 | def iscatalog(obj): 60 | return obj == sage_catalog or obj in sage_catalogs 61 | 62 | def _get_name(obj, standalone=False): 63 | if hasattr(obj, '_name'): 64 | return obj._name 65 | if hasattr(obj, 'name'): 66 | try: 67 | return obj.name() 68 | except: 69 | pass 70 | if hasattr(obj, '__name__'): 71 | return obj.__name__ 72 | return _math_repr(obj, standalone) 73 | 74 | def _get_visual_widget(obj): 75 | r""" 76 | Which is the specialized widget class name for viewing this object (if any) 77 | 78 | TESTS:: 79 | 80 | sage: from sage.all import * 81 | sage: from sage_explorer._widgets import * 82 | sage: from sage_explorer.sage_explorer import _get_visual_widget 83 | sage: p = Partition([3,3,2,1]) 84 | sage: _get_visual_widget(p).__class__ 85 | 86 | sage: f(x) = x^2 87 | sage: w = _get_visual_widget(f) 88 | sage: w.name 89 | 'x |--> x^2' 90 | """ 91 | if isclass(obj) or ismodule(obj) or iscatalog(obj): 92 | return 93 | if hasattr(obj, "_widget_"): 94 | return obj._widget_() 95 | if (hasattr(obj, 'number_of_arguments') and obj.number_of_arguments() < 2) \ 96 | or (hasattr(obj, 'plot') and not hasattr(obj, 'number_of_arguments')): 97 | from ._widgets import PlotWidget 98 | return PlotWidget(obj) 99 | 100 | def _math_repr(obj, display_mode=None, standalone=False): 101 | r""" 102 | When Sage LaTeX implementation 103 | applies well to MathJax, use it. 104 | 105 | INPUT: 106 | 107 | - ``obj`` -- an object to be represented 108 | - ``display_mode`` -- string values of %display magic 109 | - ``standalone`` -- a boolean 110 | 111 | OUTPUT: a (unicode) string 112 | 113 | TESTS:: 114 | 115 | sage: from sage_explorer.sage_explorer import _math_repr 116 | sage: _math_repr(42, display_mode='latex') 117 | '$42$' 118 | sage: _math_repr(ZZ, display_mode='latex') 119 | '$\\Bold{Z}$' 120 | sage: from sage.combinat.tableau import Tableau 121 | sage: t = Tableau([[1, 2], [3], [4]]) 122 | sage: _math_repr(t) 123 | '[[1, 2], [3], [4]]' 124 | sage: _math_repr(t, display_mode='unicode_art', standalone=True) 125 | '
┌───┬───┐\n│ 1 │ 2 │\n├───┼───┘\n│ 3 │\n├───┤\n│ 4 │\n└───┘
' 126 | sage: _math_repr(t, display_mode='unicode_art', standalone=False) 127 | '[[1, 2], [3], [4]]' 128 | sage: _math_repr(0) 129 | '0' 130 | """ 131 | if obj is None: 132 | return '' 133 | if not display_mode: 134 | try: 135 | display_mode = get_display_manager().preferences.text 136 | except: 137 | display_mode = DISPLAY_MODE 138 | if display_mode=='latex' and hasattr(obj, '_latex_'): 139 | try: 140 | s = obj._latex_() 141 | except: 142 | s = str(obj) # signature is sometimes different 143 | if 'tikz' not in s and 'raisebox' not in s: 144 | return "${}$" . format(s) 145 | if display_mode=='unicode_art' and hasattr(obj, '_unicode_art_'): 146 | try: 147 | s = obj._unicode_art_() 148 | except: 149 | pass 150 | else: 151 | if standalone: # for ExplorableValue 152 | return "
{}
" . format(obj._unicode_art_()) 153 | else: # for widget labels: back to plain representation 154 | pass 155 | # not display_mode or display_mode=='plain' 156 | if hasattr(obj, '__name__') and obj.__name__ and not obj.__name__.startswith('<'): 157 | return obj.__name__ 158 | if hasattr(obj, '__str__') and obj.__str__() and not obj.__str__().startswith('<'): 159 | s = obj.__str__() 160 | else: 161 | s = obj.__doc__.strip() 162 | if '\n' in str(s): # for limited size widget labels 163 | return s[:s.find('\n')] 164 | else: 165 | return s 166 | 167 | def switch_visibility(widget, visibility): 168 | r""" 169 | Display/hide a widget with CSS. 170 | """ 171 | if visibility: 172 | widget.remove_class('invisible') 173 | widget.add_class('visible') 174 | else: 175 | widget.remove_class('visible') 176 | widget.add_class('invisible') 177 | 178 | 179 | class Title(Label): 180 | r"""A title of various levels 181 | 182 | For HTML display 183 | """ 184 | def __init__(self, value='', level=1): 185 | super(Title, self).__init__() 186 | self.value = value 187 | self.add_class('title-level%d' % level) 188 | 189 | 190 | class MathTitle(HTMLMathSingleton): 191 | r"""A title of various levels 192 | 193 | For HTML display 194 | """ 195 | def __init__(self, value='', level=1): 196 | super(MathTitle, self).__init__(value) 197 | self.value = _math_repr(value) 198 | self.add_class("title-level%d" % level) 199 | 200 | 201 | class Separator(Label): 202 | r""" 203 | A separator with a letter ot symbol in it. 204 | """ 205 | 206 | def __init__(self, s): 207 | super(Separator, self).__init__( 208 | s, 209 | layout=Layout(padding='0 4px') 210 | ) 211 | self.add_class("separator") 212 | 213 | 214 | class HelpButton(ToggleButtonSingleton): 215 | r""" 216 | """ 217 | def __init__(self, obj=None, target=None): 218 | super(HelpButton, self).__init__( 219 | description='?' 220 | ) 221 | self.add_class("separator") 222 | self.click_event = Event( 223 | source=self, 224 | watched_events=['click'] 225 | ) 226 | self.set_target(obj, target) 227 | 228 | def set_focusable(self, focusable): 229 | r""" 230 | For compatibility. 231 | """ 232 | if focusable is True: 233 | self.allow_focus() 234 | else: 235 | self.disallow_focus() 236 | 237 | def set_target(self, obj, target): 238 | def open_help(event): 239 | if obj and target: 240 | if self.value: 241 | target.content = obj.__doc__ 242 | switch_visibility(target, True) 243 | else: 244 | target.reset() 245 | self.click_event._dom_handlers.callbacks.clear() # Remove previous handler 246 | self.click_event.on_dom_event(open_help) # Display `obj` help on click 247 | 248 | 249 | class ExplorableHistory(deque): 250 | 251 | def __init__(self, obj=None, initial_name=None, previous_history=[]): 252 | super(ExplorableHistory, self).__init__(previous_history) 253 | if obj is not None: 254 | self.append(obj) 255 | self.initial_name = self.get_initial_name(value=obj) 256 | 257 | @staticmethod 258 | def get_initial_name(value=None, test_sh_hist=[]): 259 | r"""Attempt to deduce the widget value variable name 260 | from notebook input history. 261 | In case it is not found, or not a string, set to `Hist[0]`. 262 | 263 | TESTS:: 264 | 265 | sage: from sage_explorer.sage_explorer import ExplorableHistory 266 | sage: h = ExplorableHistory() 267 | sage: h.get_initial_name(value=42) is None 268 | True 269 | sage: import __main__ 270 | sage: eval(compile('x=42','', 'exec')) 271 | sage: x 272 | 42 273 | sage: h.get_initial_name(value=42, test_sh_hist=["w = explore(42)", "w"]) is None 274 | True 275 | sage: h.get_initial_name(value=42, test_sh_hist=["x=42", "w = explore(x)", "w"]) 276 | 'x' 277 | sage: h.get_initial_name(value=42, test_sh_hist=["x=42", "w = explore(x)", "explore(43)", "w"]) 278 | 'x' 279 | """ 280 | initial_name = None 281 | try: 282 | sh_hist = get_ipython().history_manager.input_hist_parsed[-50:] 283 | test_locs = {} 284 | except: 285 | sh_hist = test_sh_hist # We are in the test environment 286 | test_locs = {'x': 42} 287 | sh_hist.reverse() 288 | for l in sh_hist: 289 | if 'explore' in l: 290 | m = re.search(r'explore[ ]*\([ ]*([^)]+)\)', l) 291 | if m: 292 | initial_name_candidate = m.group(1).strip() 293 | try: 294 | if initial_name_candidate[0].isdigit(): 295 | continue 296 | except: 297 | if not value: 298 | return initial_name_candidate 299 | try: 300 | if _eval_in_main(initial_name_candidate, locals=test_locs) == value: 301 | initial_name = initial_name_candidate 302 | break 303 | except: 304 | pass 305 | return initial_name 306 | 307 | def push(self, obj): 308 | r""" 309 | Push the history, ie append 310 | an object and increment index. 311 | 312 | TESTS:: 313 | 314 | sage: from sage_explorer.sage_explorer import ExplorableHistory 315 | sage: h = ExplorableHistory(42) 316 | sage: h.push("An object") 317 | sage: h 318 | ExplorableHistory([42, 'An object']) 319 | """ 320 | self.append(obj) 321 | self.truncate(MAX_LEN_HISTORY) 322 | 323 | def pop(self, n=1): 324 | r""" 325 | Pop the history, ie pop the list 326 | and decrement index. 327 | 328 | TESTS:: 329 | 330 | sage: from sage_explorer.sage_explorer import ExplorableHistory 331 | sage: h = ExplorableHistory("A first value") 332 | sage: h.push(42) 333 | sage: h.pop() 334 | 42 335 | sage: h 336 | ExplorableHistory(['A first value']) 337 | sage: h.pop() 338 | Traceback (most recent call last): 339 | ... 340 | Exception: No more history! 341 | sage: h = ExplorableHistory(1) 342 | sage: for i in range(2,6): h.push(i) 343 | sage: h.pop(4) 344 | 2 345 | sage: h 346 | ExplorableHistory([1]) 347 | """ 348 | for i in range(n): 349 | val = super(ExplorableHistory, self).pop() 350 | if not self: 351 | raise Exception("No more history!") 352 | return val 353 | 354 | def get_item(self, i=None): 355 | r""" 356 | Pop the history, ie pop the list 357 | and decrement index. 358 | 359 | TESTS:: 360 | 361 | sage: from sage_explorer.sage_explorer import ExplorableHistory 362 | sage: h = ExplorableHistory("A first value") 363 | sage: h.push(42) 364 | sage: h.get_item(1) 365 | 42 366 | sage: h.get_item(0) 367 | 'A first value' 368 | sage: h.get_item() 369 | 42 370 | """ 371 | if i is None: 372 | return self[-1] 373 | return self.__getitem__(i) 374 | 375 | def make_menu_options(self): 376 | r""" 377 | Truncate the history, ie pop values 378 | from the start, until list becomes small enough. 379 | 380 | TESTS:: 381 | 382 | sage: from sage_explorer.sage_explorer import ExplorableHistory 383 | sage: h = ExplorableHistory("A first value") 384 | sage: h.make_menu_options() 385 | [('Hist[0]: A first value', 0)] 386 | sage: for i in range(2): h.push(i) 387 | sage: h.make_menu_options() 388 | [('Hist[0]: A first value', 0), ('Hist[1]: 0', 1), ('Hist[2]: 1', 2)] 389 | """ 390 | def make_option(label, i): 391 | return ("{}: {}".format(label, _get_name(self[i])), i) 392 | first_label = self.initial_name or "Hist[0]" 393 | return [make_option(first_label, 0)] + \ 394 | [make_option("Hist[{}]". format(i+1), i+1) for i in range(self.__len__()-1)] 395 | 396 | def truncate(self, max=MAX_LEN_HISTORY): 397 | r""" 398 | Truncate the history, ie pop values 399 | from the start, until list becomes small enough. 400 | 401 | TESTS:: 402 | 403 | sage: from sage_explorer.sage_explorer import ExplorableHistory 404 | sage: h = ExplorableHistory("A first value") 405 | sage: for i in range(55): h.push(i) 406 | sage: len(h) 407 | 50 408 | sage: h.truncate(10) 409 | sage: h 410 | ExplorableHistory([45, 46, 47, 48, 49, 50, 51, 52, 53, 54]) 411 | """ 412 | shift = self.__len__() - max 413 | if shift < 1: 414 | return 415 | for i in range(shift): 416 | self.popleft() 417 | 418 | 419 | class ExplorableValue(HTMLMathSingleton): 420 | r""" 421 | A repr string with a link to a Sage object. 422 | 423 | TESTS:: 424 | 425 | sage: from sage_explorer.sage_explorer import ExplorableValue 426 | sage: v = ExplorableValue(42) 427 | sage: v.new_val 428 | sage: e = {'type': 'click'} 429 | sage: v.click_event._dom_handlers.callbacks[0](e) 430 | sage: v.new_val 431 | 42 432 | """ 433 | explorable = Any() # Some computed math object 434 | new_val = Any() # Overall value. Will be changed when explorable is clicked. `value` being reserved by ipywidgets. 435 | 436 | def __init__(self, explorable, display=None, initial_value=None): 437 | if type(explorable) is type(int(1)): # a hack for non-Sage integers 438 | from sage.rings.integer import Integer 439 | self.explorable = Integer(explorable) 440 | else: 441 | self.explorable = explorable 442 | if initial_value is not None: 443 | self.new_val = initial_value 444 | super(ExplorableValue, self).__init__(layout=Layout(margin='1px')) 445 | self.add_class('explorable-value') 446 | self._tooltip = "Click to explore this value" 447 | self.reset(display) 448 | self.click_event = Event( 449 | source=self, 450 | watched_events=['click', 'keyup'] 451 | ) 452 | def set_new_val(event): 453 | r""" 454 | Check event type and key, 455 | then copy `explorable` to `new_val`. 456 | 457 | INPUT: 458 | 459 | - ``event`` -- a dictionary 460 | """ 461 | if event['type'] == 'click' or event['key'] == 'Enter': 462 | self.new_val = self.explorable 463 | self.click_event.on_dom_event(set_new_val) # Handle clicking 464 | 465 | 466 | def reset(self, display): 467 | r""" 468 | `explorable` has changed: compute HTML value. 469 | """ 470 | if display: 471 | self.value = display 472 | else: 473 | self.value = _get_name(self.explorable, standalone=True) 474 | 475 | 476 | class ExplorableCell(Box): 477 | r""" 478 | A text box that contains one or several explorable value(s). 479 | 480 | TESTS:: 481 | 482 | sage: from sage_explorer.sage_explorer import ExplorableCell 483 | sage: c = ExplorableCell(42) 484 | sage: len(c.children) 485 | 1 486 | sage: c = ExplorableCell(ZZ) 487 | sage: len(c.children) 488 | 1 489 | sage: c = ExplorableCell([42, 'a string', ZZ]) 490 | sage: len(c.children) 491 | 7 492 | """ 493 | explorable = Any() # can be a single value or a list or a tuple 494 | new_val = Any() # when [one of] the value(s) is clicked 495 | 496 | def __init__(self, explorable, initial_value=None, **kws): 497 | r""" 498 | A text box to display explorable value(s). 499 | """ 500 | self.explorable = explorable 501 | if initial_value is not None: 502 | self.new_val = initial_value 503 | super(ExplorableCell, self).__init__(**kws) 504 | self.reset() 505 | 506 | def reset(self): 507 | r""" 508 | `explorable` has changed: compute all content. 509 | """ 510 | children = [] 511 | self.explorables = [] 512 | if type(self.explorable) in [type([]), type(()), set, frozenset]: 513 | if type(self.explorable) == type([]): 514 | children.append(Separator('[')) 515 | elif type(self.explorable) == type(()): 516 | children.append(Separator('(')) 517 | else: # Here, make both the set and its elements explorable 518 | ev = ExplorableValue( 519 | self.explorable, 520 | display='{', 521 | initial_value=self.new_val 522 | ) 523 | dlink((ev, 'new_val'), (self, 'new_val')) 524 | children.append(ev) 525 | for e in self.explorable: 526 | ev = ExplorableValue(e, initial_value=self.new_val) 527 | dlink((ev, 'new_val'), (self, 'new_val')) # Propagate click 528 | self.explorables.append(ev) 529 | children.append(ev) 530 | children.append(Separator(',')) 531 | children.pop() 532 | if type(self.explorable) == type([]): 533 | children.append(Separator(']')) 534 | elif type(self.explorable) == type(()): 535 | children.append(Separator(')')) 536 | else: 537 | children.append(Separator('}')) 538 | elif self.explorable is not None: # treated as a single value 539 | ev = ExplorableValue(self.explorable, initial_value=self.new_val) 540 | self.explorables.append(ev) 541 | dlink((ev, 'new_val'), (self, 'new_val')) # Propagate click 542 | children.append(ev) 543 | self.children = children 544 | 545 | def set_focusable(self, focusable): 546 | r""" 547 | For compatibility. 548 | """ 549 | if focusable is True: 550 | for ev in self.explorables: 551 | ev.allow_focus() 552 | elif focusable is False: 553 | for ev in self.explorables: 554 | ev.disallow_focus() 555 | 556 | 557 | class ExplorerComponent(Box): 558 | r""" 559 | Common methods to all components. 560 | 561 | TESTS:: 562 | 563 | sage: from sage_explorer.sage_explorer import ExplorerComponent 564 | sage: c = ExplorerComponent("Initial value") 565 | sage: c.value = 42 566 | """ 567 | value = Any() 568 | _tooltip = Unicode('') 569 | _tooltip_visibility = Bool(True) 570 | 571 | def __init__(self, obj, **kws): 572 | r""" 573 | Common methods to all components. 574 | 575 | TESTS:: 576 | 577 | sage: from sage_explorer.sage_explorer import ExplorerComponent 578 | sage: c = ExplorerComponent("Initial value") 579 | sage: c.value = 42 580 | """ 581 | self.donottrack = True 582 | self.value = obj 583 | super(ExplorerComponent, self).__init__(**kws) 584 | self.reset() 585 | self.donottrack = False 586 | 587 | @observe('_tooltip_visibility') 588 | def tooltip_visibility_changed(self, change): 589 | if not self._tooltip: 590 | return 591 | for c in self.children: 592 | if hasattr(c, 'set_tooltip'): 593 | if change.new: 594 | c.set_tooltip(self._tooltip) 595 | else: 596 | c.set_tooltip() 597 | 598 | def set_focusable(self, focusable): 599 | if hasattr(self, 'allow_focus'): # a Singleton 600 | if focusable is True: 601 | self.allow_focus() 602 | elif focusable is False: 603 | self.disallow_focus() 604 | elif hasattr(self, 'children'): 605 | for child in self.children: 606 | if hasattr(child, 'allow_focus'): 607 | if focusable is True: 608 | child.allow_focus() 609 | elif focusable is False: 610 | child.disallow_focus() 611 | 612 | @abstractmethod 613 | def reset(self): 614 | r""" 615 | Reset component when `value` is changed. 616 | 617 | TESTS:: 618 | 619 | sage: from sage_explorer.sage_explorer import ExplorerComponent 620 | sage: c = ExplorerComponent("Initial value") 621 | sage: c.value = 42 622 | """ 623 | pass 624 | 625 | @observe('value') 626 | def value_changed(self, change): 627 | r""" 628 | What to do when the value has been changed. 629 | 630 | INPUT: 631 | 632 | - ``change`` -- a change Bunch 633 | 634 | TESTS:: 635 | 636 | sage: from sage_explorer.sage_explorer import ExplorerComponent 637 | sage: obj = Tableau([[1, 2, 5, 6], [3], [4]]) 638 | sage: new_obj = 42 639 | sage: p = ExplorerComponent(obj) 640 | sage: p.value = new_obj 641 | 642 | """ 643 | if self.donottrack: 644 | return 645 | old_val = change.old 646 | new_val = change.new 647 | actually_changed = (id(new_val) != id(old_val)) 648 | if actually_changed: 649 | self.reset() 650 | 651 | @abstractmethod 652 | def on_submit(self, callback, remove=False): 653 | """(Un)Register a callback to handle 'submit' event. 654 | Triggered when the user clicks enter. 655 | Abstract method. 656 | 657 | Parameters 658 | ---------- 659 | callback: callable 660 | Will be called with exactly one argument: the Widget instance 661 | remove: bool (optional) 662 | Whether to unregister the callback 663 | """ 664 | pass 665 | 666 | 667 | class ExplorerTitle(ExplorerComponent): 668 | r"""The sage explorer title bar 669 | """ 670 | content = Unicode('') 671 | 672 | def __init__(self, obj): 673 | self.donottrack = True 674 | super(ExplorerTitle, self).__init__( 675 | obj, 676 | children=( 677 | MathTitle('', 2), 678 | global_css_code), 679 | layout=Layout(padding='5px 10px') 680 | ) 681 | self.donottrack = False 682 | self.add_class("explorer-title") 683 | 684 | def reset(self): 685 | if _get_name(self.value): 686 | self.content = _get_name(self.value) 687 | else: 688 | self.content = '{}' . format(_get_name(self.value)) 689 | self.children[0].value = "Exploring: {}" . format(self.content) 690 | 691 | 692 | class ExplorerDescription(ExplorerComponent): 693 | r"""The sage explorer object description 694 | """ 695 | content = Unicode('') 696 | 697 | def __init__(self, obj, help_target=None): 698 | self.help_target = None 699 | self._tooltip = "Click for full documentation" 700 | super(ExplorerDescription, self).__init__( 701 | obj, 702 | children=( 703 | HTMLMathSingleton(), 704 | HelpButton(obj, help_target) 705 | ) 706 | ) 707 | self.add_class("explorer-description") 708 | if help_target: 709 | self.set_help_target(help_target) 710 | dlink((self, 'content'), (self.children[0], 'value')) 711 | 712 | def set_help_target(self, target): 713 | self.help_target = target 714 | self.children[1].set_target(self.value, target) 715 | def open_help(event): 716 | if event['key'] in ['?', 'Enter'] and self.value and target: 717 | target.content = self.value.__doc__ 718 | switch_visibility(target, True) 719 | self.children[1].value = True 720 | keyboard_event = Event( 721 | source=self.children[0], 722 | watched_events=['keyup'] 723 | ) 724 | keyboard_event.on_dom_event(open_help) # Display `self.value` help on '?'/'Enter' 725 | 726 | def reset(self): 727 | if self.value.__doc__: 728 | self.content = [l for l in self.value.__doc__.split("\n") if l][0].strip() 729 | else: 730 | self.content = '' 731 | if self.help_target: 732 | self.set_help_target(self.help_target) # re-recreate help button handler 733 | if self._tooltip: 734 | self.children[1].set_tooltip(self._tooltip) 735 | 736 | 737 | class ExplorerProperties(ExplorerComponent, GridBox): 738 | r""" 739 | Display object properties as a table. 740 | 741 | TESTS:: 742 | 743 | sage: from sage_explorer.sage_explorer import ExplorerProperties 744 | sage: p = ExplorerProperties(42) 745 | """ 746 | def __init__(self, obj): 747 | super(ExplorerProperties, self).__init__( 748 | obj, 749 | layout=Layout(border='1px solid #eee', width='100%', grid_template_columns='auto auto') 750 | ) 751 | self.add_class("explorer-table") 752 | 753 | def reset(self): 754 | children = [] 755 | self.properties = [] 756 | self.explorables = [] 757 | for p in get_properties(self.value, Settings.properties): 758 | self.properties.append(p) 759 | explorable = getattr(self.value, p.name).__call__() 760 | children.append(Box((Label(p.prop_label),), layout=Layout(border='1px solid #eee'))) 761 | e = ExplorableCell(explorable, initial_value=self.value) 762 | self.explorables.append(e) 763 | dlink((e, 'new_val'), (self, 'value')) # Propagate explorable if clicked 764 | children.append(e) 765 | self.children = children 766 | 767 | 768 | class ExplorerVisual(ExplorerComponent): 769 | r""" 770 | The sage explorer visual representation 771 | """ 772 | def __init__(self, obj): 773 | super(ExplorerVisual, self).__init__( 774 | obj, 775 | layout = Layout(right='0') 776 | ) 777 | 778 | def reset(self): 779 | w = _get_visual_widget(self.value) 780 | if hasattr(w, 'disallow_inside_focus'): 781 | w.disallow_inside_focus() 782 | if w: 783 | self.children = (w,) 784 | else: 785 | if hasattr(self.value, '__ascii_art__'): 786 | self.children = ( 787 | TextareaSingleton( 788 | repr(self.value._ascii_art_()), 789 | rows=8 790 | ),) 791 | else: 792 | self.children = () 793 | if self.children: 794 | dlink((self.children[0], 'value'), (self, 'value')) 795 | 796 | 797 | class ExplorerHistory(ExplorerComponent): 798 | r""" 799 | A text input to give a name to a math object 800 | """ 801 | _history = Instance(ExplorableHistory) 802 | _history_len = Int() 803 | _history_index = Int() 804 | 805 | def __init__(self, obj, history=None): 806 | r""" 807 | Which is the specialized widget class name for viewing this object (if any) 808 | 809 | TESTS:: 810 | 811 | sage: from sage_explorer.sage_explorer import ExplorerHistory, ExplorableHistory 812 | sage: h = ExplorerHistory('Initial value') 813 | sage: h._history 814 | ExplorableHistory(['Initial value']) 815 | sage: h._history.push(42) 816 | sage: h._history = ExplorableHistory(43, previous_history=list(h._history)) 817 | sage: h._history 818 | ExplorableHistory(['Initial value', 42, 43]) 819 | """ 820 | self.donottrack = True 821 | self._history = history or ExplorableHistory(obj) 822 | super(ExplorerHistory, self).__init__( 823 | obj, 824 | children=(DropdownSingleton( 825 | layout=Layout(width='7em', padding='0', margin='0') 826 | ),), 827 | layout=Layout(padding='0') 828 | ) 829 | self.donottrack = False 830 | # User input 831 | def dropdown_selection(change): 832 | if self.donottrack: 833 | return 834 | self.donottrack = True 835 | self._history_index = change.new 836 | self.value = self._history.get_item(change.new) 837 | self.donottrack = False 838 | self.children[0].observe(dropdown_selection, names='value') 839 | 840 | def reset(self): 841 | r""" 842 | Value has changed. 843 | """ 844 | self.compute_dropdown() 845 | 846 | def compute_dropdown(self): 847 | r""" 848 | History has changed 849 | """ 850 | self.children[0].options = self._history.make_menu_options() 851 | self.children[0].value = self._history_index 852 | if self._history_len > 1: 853 | self.children[0].disabled = False 854 | self.children[0]._tooltip = 'Click to show history' 855 | else: 856 | self.children[0].disabled = True 857 | self.children[0]._tooltip = '' 858 | 859 | @observe('_history_len') 860 | def history_changed(self, change): 861 | r""" 862 | _history_len was changed by means of explorer navigation (click) 863 | """ 864 | if self.donottrack: 865 | return 866 | self.donottrack = True 867 | self.compute_dropdown() 868 | self.donottrack = False 869 | 870 | 871 | class ExplorerMethodSearch(ExplorerComponent): 872 | r""" 873 | A widget to search a method 874 | 875 | TESTS:: 876 | 877 | sage: from sage_explorer.sage_explorer import ExplorerMethodSearch 878 | sage: s = ExplorerMethodSearch(42) 879 | """ 880 | explored = Instance(ExploredMember) # to share with ExplorerArgs and ExplorerHelp 881 | 882 | def __init__(self, obj, help_target=None, menu_type="combo"): 883 | if menu_type == "dropdown": 884 | menu_widget_class = DropdownSingleton 885 | else: 886 | menu_widget_class = ComboboxSingleton 887 | super(ExplorerMethodSearch, self).__init__( 888 | obj, 889 | children=( 890 | menu_widget_class( 891 | placeholder="Enter name ; use '?' for help" 892 | ),) 893 | ) 894 | if help_target: 895 | self.set_help_target(help_target) 896 | def method_changed(change): 897 | selected_method = change.new 898 | if selected_method in self.members_dict: 899 | self.explored = self.members_dict[selected_method] 900 | # we do not link directly for not all names deserve a computation 901 | self.children[0].observe(method_changed, names='value') 902 | self.children[0]._submission_callbacks = CallbackDispatcher() 903 | 904 | def set_display(self, s): 905 | self.children[0].value = s 906 | 907 | def reset(self): 908 | r""" 909 | Setup the combobox. 910 | """ 911 | if isclass(self.value) or ismodule(self.value) or iscatalog(self.value): 912 | cls = self.value 913 | else: 914 | cls = self.value.__class__ 915 | self.members = get_members(cls, Settings.properties) # Here, we both have a list and a dict 916 | self.members_dict = {m.name: m for m in self.members} 917 | self.children[0].options=[m.name for m in self.members] 918 | try: 919 | self.children[0].value = '' # case Combobox 920 | except: 921 | self.children[0].value = None # case Dropdown 922 | self.explored = ExploredMember('') 923 | 924 | def set_help_target(self, target): 925 | if not target: 926 | return 927 | def open_help(event): 928 | if event['key'] == '?' and self.explored: 929 | if not hasattr(self.explored, 'doc'): 930 | self.explored.compute_doc() 931 | target.content = self.explored.doc 932 | switch_visibility(target, True) 933 | click_event = Event( 934 | source=self, 935 | watched_events=['keyup'] 936 | ) 937 | click_event.on_dom_event(open_help) # Display `explored` help on click 938 | 939 | def on_submit(self, callback, remove=False): 940 | self.children[0].on_submit(callback, remove) 941 | 942 | 943 | class ExplorerArgs(ExplorerComponent): 944 | r""" 945 | A text box to input method arguments 946 | """ 947 | content = Unicode('') 948 | explored = Instance(ExploredMember) # shared by ExplorerMethodSearch 949 | 950 | def __init__(self, obj=None): 951 | r""" 952 | A text box to input method arguments. 953 | 954 | TESTS:: 955 | 956 | sage: from sage_explorer.sage_explorer import ExplorerArgs 957 | sage: a = ExplorerArgs(42) 958 | """ 959 | self.default_placeholder = "Enter arguments ; for example: 3,7,pi=3.14" 960 | super(ExplorerArgs, self).__init__( 961 | obj, 962 | children=(TextSingleton( 963 | '', 964 | layout=Layout(width="100%") 965 | ),) 966 | ) 967 | self.add_class("explorer-flexitem") 968 | def explored_changed(change): 969 | explored = change.new 970 | if not explored.name: 971 | self.reset() 972 | return 973 | if not hasattr(explored, 'args'): 974 | explored.compute_argspec() 975 | args, defaults = explored.args, explored.defaults 976 | if args and args != ['self']: 977 | self.children[0].disabled = False 978 | if defaults: 979 | self.children[0].placeholder = str(defaults) 980 | else: 981 | self.children[0].placeholder = self.default_placeholder 982 | else: 983 | self.children[0].value = '' 984 | self.children[0].placeholder = '' 985 | self.children[0].disabled = True 986 | self.observe(explored_changed, names='explored') 987 | self.children[0]._submission_callbacks = CallbackDispatcher() 988 | dlink((self.children[0], 'value'), (self, 'content')) 989 | 990 | def reset(self): 991 | self.children[0].value = '' 992 | self.children[0].disabled = False 993 | self.children[0].placeholder = self.default_placeholder 994 | 995 | def on_submit(self, callback, remove=False): 996 | self.children[0].on_submit(callback, remove) 997 | 998 | 999 | class ExplorerRunButton(ButtonSingleton): 1000 | r""" 1001 | A button for running methods in the explorer. 1002 | 1003 | TESTS:: 1004 | 1005 | sage: from sage_explorer.sage_explorer import ExplorerRunButton 1006 | sage: b = ExplorerRunButton() 1007 | """ 1008 | def __init__(self): 1009 | super(ExplorerRunButton, self).__init__( 1010 | description = 'Run!', 1011 | tooltip = 'Evaluate the method with the specified arguments', 1012 | layout = Layout(width='4em', right='0') 1013 | ) 1014 | 1015 | def set_focusable(self, focusable): 1016 | r""" 1017 | For compatibility. 1018 | """ 1019 | if focusable is True: 1020 | self.allow_focus() 1021 | elif focusable is False: 1022 | self.disallow_focus() 1023 | 1024 | 1025 | class ExplorerOutput(ExplorerComponent): 1026 | r""" 1027 | A text box to output method results. 1028 | 1029 | TESTS:: 1030 | 1031 | sage: from sage_explorer.sage_explorer import ExplorerOutput 1032 | sage: o = ExplorerOutput(42) 1033 | """ 1034 | def __init__(self, obj=None, explorable=None): 1035 | r""" 1036 | A text box to output method results. 1037 | """ 1038 | self.output = ExplorableCell(explorable, initial_value=obj) 1039 | self.output.add_class('invisible') 1040 | def output_changed(change): 1041 | change.owner.reset() 1042 | if change.new is not None: 1043 | switch_visibility(change.owner, True) 1044 | else: 1045 | switch_visibility(change.owner, False) 1046 | self.output.observe(output_changed, names='explorable') # display/hide output 1047 | self.error = HTML("") 1048 | self.error.add_class("ansi-red-fg") 1049 | super(ExplorerOutput, self).__init__( 1050 | obj, 1051 | children=(self.output, self.error), 1052 | layout = Layout(padding='2px 50px 2px 2px') 1053 | ) 1054 | 1055 | def reset(self): 1056 | self.output.new_val = self.value 1057 | self.output.explorable = None 1058 | switch_visibility(self.output, False) 1059 | self.error.value = '' 1060 | dlink((self.output, 'new_val'), (self, 'value')) # propagate if output is clicked 1061 | 1062 | def set_output(self, obj): 1063 | self.output.explorable = obj 1064 | self.output.value = _get_name(obj) 1065 | self.error.value = '' 1066 | #self.output.switch_visibility(True) 1067 | 1068 | def set_error(self, err): 1069 | self.output.explorable = None 1070 | self.output.value = '' 1071 | self.error.value = 'Error: {}' .format(err) 1072 | 1073 | 1074 | class ExplorerHelp(ExplorerComponent): 1075 | r""" 1076 | An expandable box for object or method help text. 1077 | 1078 | TESTS:: 1079 | 1080 | sage: from sage_explorer.sage_explorer import ExplorerHelp 1081 | sage: h = ExplorerHelp(42) 1082 | """ 1083 | content = Unicode('') 1084 | explored = Instance(ExploredMember) # shared by ExplorerMethodSearch 1085 | 1086 | def __init__(self, obj): 1087 | r""" 1088 | A box for object or method help text. 1089 | """ 1090 | super(ExplorerHelp, self).__init__( 1091 | obj, 1092 | children=(HTMLMathSingleton(),), 1093 | layout=Layout(width='99%', padding='0', border='1px solid grey') 1094 | ) 1095 | def explored_changed(change): 1096 | explored = change.new 1097 | if explored.name: 1098 | if not hasattr(explored, 'doc'): 1099 | explored.compute_doc() 1100 | self.content = explored.doc 1101 | switch_visibility(self, True) 1102 | else: 1103 | self.content = '' 1104 | switch_visibility(self, False) 1105 | self.observe(explored_changed, names='explored') 1106 | 1107 | def reset(self): 1108 | self.donottrack = False 1109 | try: 1110 | self.content = sphinxify(self.value.__doc__) 1111 | except: 1112 | self.content = "Cannot retrieve help!" 1113 | switch_visibility(self, False) 1114 | 1115 | @observe('content') 1116 | def content_changed(self, change): 1117 | r""" 1118 | Actually display the docstring 1119 | """ 1120 | if self.donottrack: 1121 | return 1122 | if change.new: 1123 | formatted_content = sphinxify(change.new) 1124 | if 'text/html' in formatted_content and formatted_content['text/html']: 1125 | self.children[0].value = formatted_content['text/html'] 1126 | elif 'text/plain' in formatted_content: 1127 | self.children[0].value = formatted_content['text/plain'] 1128 | else: # case sphinxify=str 1129 | self.children[0].value = formatted_content 1130 | else: 1131 | self.children[0].value = '' 1132 | 1133 | 1134 | class ExplorerCodeCell(ExplorerComponent): 1135 | r""" 1136 | A box containing a code cell. 1137 | 1138 | TESTS:: 1139 | 1140 | sage: from sage_explorer.sage_explorer import ExplorerCodeCell 1141 | sage: cc = ExplorerCodeCell(42) 1142 | """ 1143 | content = Unicode('') 1144 | new_val = Any() 1145 | 1146 | def __init__(self, obj, standalone=False): 1147 | super(ExplorerCodeCell, self).__init__( 1148 | obj, 1149 | children=(TextareaSingleton( 1150 | placeholder="Enter code ; shift-enter to evaluate", 1151 | description_tooltip="Special values: Use '_' for your object, 'Hist' for our history\nand '__explorer__' for the current explorer.\nExamples:\n 3*_ + 1 + Hist[1]", 1152 | rows = 0, 1153 | layout=Layout(border='1px solid #eee', width='99%') 1154 | ),) 1155 | ) 1156 | link((self.children[0], 'value'), (self, 'content')) 1157 | self.run_event = Event( 1158 | source=self.children[0], 1159 | watched_events=['keyup'] 1160 | ) 1161 | try: 1162 | from sage.misc.misc import get_main_globals 1163 | self.globals = get_main_globals() 1164 | except: 1165 | self.globals = globals() 1166 | def launch_evaluation(event): 1167 | if event['key'] == 'Enter' and (event['shiftKey'] or event['ctrlKey']): 1168 | self.evaluate() 1169 | #self.children[0].value = str(self.new_val) 1170 | if standalone: # actually we want to trigger that from the explorer 1171 | self.run_event.on_dom_event(launch_evaluation) 1172 | 1173 | def reset(self): 1174 | self.content = '' 1175 | self.new_val = None 1176 | 1177 | def evaluate(self, l=None, o=None, e=None): 1178 | r""" 1179 | Evaluate the code cell 1180 | `l` being a dictionary of locals. 1181 | 1182 | INPUT: 1183 | 1184 | * `l` -- a locals dictionary ; defaults to {"_": self.value} 1185 | * `o` -- an output widget 1186 | * `e` -- an error output widget 1187 | 1188 | TESTS:: 1189 | 1190 | sage: from sage_explorer.sage_explorer import ExplorerCodeCell 1191 | sage: c = ExplorerCodeCell(42) 1192 | sage: c.content = "1 + 2" 1193 | sage: c.evaluate() 1194 | sage: c.new_val 1195 | 3 1196 | """ 1197 | g = self.globals 1198 | l = l or {"_": self.value} 1199 | local_names = l.keys() 1200 | code = compile(self.content, '', 'eval') 1201 | try: 1202 | result = eval(code, g, l) 1203 | except Exception as err: 1204 | if e: 1205 | e.set_error(err) 1206 | else: 1207 | self.content = "Evaluation error: %s" % err 1208 | self.add_class("error") 1209 | return 1210 | if result is None: # the code may have triggered some assignments 1211 | self.content = "result is None" 1212 | for name, value in l.items(): 1213 | if name not in local_names: 1214 | g[name] = value 1215 | elif o: # output somewhere else 1216 | o.set_output(result) 1217 | self.reset() 1218 | else: # output here 1219 | self.content = str(result) 1220 | self.new_val = result 1221 | 1222 | 1223 | """ 1224 | DEFAULT_COMPONENTS = [ 1225 | ExplorerTitle, 1226 | ExplorerDescription, 1227 | ExplorerProperties, 1228 | ExplorerVisual, 1229 | ExplorerHistory, 1230 | ExplorerMethodSearch, 1231 | ExplorerArgs, 1232 | ExplorerRunButton, 1233 | ExplorerOutput, 1234 | ExplorerHelp, 1235 | ExplorerCodeCell 1236 | ]""" 1237 | DEFAULT_COMPONENTS = { 1238 | 'titlebox': ExplorerTitle, 1239 | 'descriptionbox': ExplorerDescription, 1240 | 'propsbox': ExplorerProperties, 1241 | 'visualbox': ExplorerVisual, 1242 | 'histbox': ExplorerHistory, 1243 | 'searchbox': ExplorerMethodSearch, 1244 | 'argsbox': ExplorerArgs, 1245 | 'runbutton': ExplorerRunButton, 1246 | 'outputbox': ExplorerOutput, 1247 | 'helpbox': ExplorerHelp, 1248 | 'codebox': ExplorerCodeCell 1249 | } 1250 | 1251 | class SageExplorer(VBox): 1252 | r""" 1253 | Sage-Explorer: interactive exploration of SageMath objects in Jupyter 1254 | 1255 | INPUT: 1256 | 1257 | - `o` -- an object 1258 | 1259 | OUTPUT: a Jupyter widget 1260 | 1261 | Running `explore(o)` opens an interactive page displaying `o` 1262 | together with contextual information: 1263 | - rich display(s) of the object (e.g. LaTeX formula, picture, or 1264 | interactive widget depending on availability; 1265 | - a selection of properties, that is relevant invariants or 1266 | related objects; 1267 | - a list of operations (methods) available for the object 1268 | - documentation. 1269 | 1270 | Following the metaphor of a web browser, the user can then 1271 | visually explore SageMath by navigating between objects along 1272 | properties or method calls. 1273 | 1274 | EXAMPLES: 1275 | 1276 | Explore various objects:: 1277 | 1278 | sage: from sage_explorer import explore 1279 | 1280 | sage: explore(SymmetricGroup(3)) 1281 | SageExplorer for Symmetric group of order 3! as a permutation group with properties 'an_element', 'cardinality', 'category', 'multiplication_table' 1282 | 1283 | sage: explore(Partition([3,2,1,1])) 1284 | SageExplorer for [3, 2, 1, 1] with properties 'conjugate', 'hook_lengths', 'parent' 1285 | 1286 | sage: explore(graphs.PetersenGraph()) 1287 | SageExplorer for Petersen graph 1288 | 1289 | Explore Sage's catalog of graphs:: 1290 | 1291 | sage: explore(graphs) 1292 | SageExplorer for }], 1309 | ... 1310 | 1311 | This adds the property ``number of vertices`` to Sage's graphs:: 1312 | 1313 | sage: explore.settings.add_property('num_verts', 1314 | ....: instance_of=Graph, 1315 | ....: label='number of vertices') 1316 | sage: explore(graphs.PetersenGraph()) 1317 | SageExplorer for Petersen graph with property 'num_verts' 1318 | 1319 | Users are most welcome to suggest additions to the default 1320 | configuration, e.g. by contacting the authors or posting an issue 1321 | on `Sage-Explorer's GitHub repository `_. 1322 | """ 1323 | 1324 | value = Any() 1325 | _history = Instance(ExplorableHistory) 1326 | _history_len = Int() 1327 | _history_index = Int() 1328 | components = Dict() # A list of widgets ; really a trait ? 1329 | _display_settings = Dict() 1330 | _properties_settings = Dict() 1331 | 1332 | def __init__(self, obj=None, components=DEFAULT_COMPONENTS, test_mode=False): 1333 | """ 1334 | TESTS:: 1335 | 1336 | sage: from sage_explorer.sage_explorer import SageExplorer 1337 | sage: t = StandardTableaux(15).random_element() 1338 | sage: widget = SageExplorer(t, test_mode=True) 1339 | sage: type(widget.value) 1340 | 1341 | sage: len(widget._history) 1342 | 1 1343 | """ 1344 | self.test_mode = test_mode 1345 | self.donottrack = True # Prevent any interactivity while creating the widget 1346 | super(SageExplorer, self).__init__() 1347 | if obj is None: 1348 | obj = sage_catalog 1349 | self.value = obj 1350 | self._history = ExplorableHistory(obj) #, initial_name=self.initial_name) 1351 | self._history_len = 1 # Needed to activate history propagation 1352 | self._history_index = 0 1353 | self.components = components 1354 | if not test_mode: 1355 | self.create_components() 1356 | self.implement_interactivity() 1357 | self.implement_settings_interactivity() 1358 | self.draw() 1359 | self.donottrack = False 1360 | 1361 | def __repr__(self): 1362 | r""" 1363 | A readable representation string. 1364 | 1365 | TESTS:: 1366 | 1367 | sage: from sage_explorer import explore 1368 | sage: explore(42) 1369 | SageExplorer for 42 with property 'parent' 1370 | sage: explore(StandardTableau([[1, 3, 4], [2], [5]])) 1371 | SageExplorer for [[1, 3, 4], [2], [5]] with properties 'charge', 'cocharge', 'conjugate', 'parent' 1372 | """ 1373 | ret = "SageExplorer for %s" % self.value 1374 | properties_names = [p.name for p in self.propsbox.properties] 1375 | if not properties_names: 1376 | return ret 1377 | if len(properties_names) < 2: 1378 | return ret + " with property '%s'" % str(properties_names[0]) 1379 | return ret + " with properties '%s'" % "', '" . join(properties_names) 1380 | 1381 | def reset(self): 1382 | self.donottrack = True 1383 | for name in self.components: 1384 | if name not in ['runbutton', 'codebox']: 1385 | setattr(getattr(self, name), 'value', self.value) 1386 | self.donottrack = False 1387 | 1388 | def create_components(self): 1389 | r""" 1390 | Create all components for the explorer. 1391 | 1392 | TESTS:: 1393 | 1394 | sage: from sage_explorer import SageExplorer 1395 | sage: e = SageExplorer(42) 1396 | sage: e.create_components() 1397 | """ 1398 | for name in self.components: 1399 | if name == 'runbutton': 1400 | setattr(self, name, self.components[name].__call__()) 1401 | elif name == 'histbox': 1402 | setattr(self, name, self.components[name].__call__( 1403 | self.value, 1404 | history=self._history 1405 | )) 1406 | else: 1407 | setattr(self, name, self.components[name].__call__(self.value)) 1408 | 1409 | def implement_interactivity(self): 1410 | r""" 1411 | Implement links and observers on explorer components. 1412 | 1413 | TESTS:: 1414 | 1415 | sage: from sage_explorer import SageExplorer 1416 | sage: e = SageExplorer(42) 1417 | sage: e.create_components() 1418 | sage: e.implement_interactivity() 1419 | """ 1420 | if self.test_mode: 1421 | self.donottrack = True # Prevent any interactivity while installing the links 1422 | if 'descriptionbox' in self.components and 'helpbox' in self.components: 1423 | self.descriptionbox.set_help_target(self.helpbox) 1424 | if 'propsbox' in self.components: 1425 | dlink((self.propsbox, 'value'), (self, 'value')) # Handle the clicks on property values 1426 | if 'visualbox' in self.components: 1427 | dlink((self.visualbox, 'value'), (self, 'value')) # Handle the visual widget changes 1428 | if 'histbox' in self.components: 1429 | dlink((self, '_history_len'), (self.histbox, '_history_len')) # Propagate clicked navigation 1430 | link((self.histbox, '_history_index'), (self, '_history_index')) # Handle history selection and propagate clicked navigation 1431 | def handle_history_selection(change): 1432 | self.donottrack = True # so we do not push history 1433 | self.value = self._history.get_item(self._history_index) 1434 | self.reset() 1435 | self.donottrack = False 1436 | self.observe(handle_history_selection, names='_history_index') 1437 | 1438 | def compute_selected_member(button=None): 1439 | if not 'searchbox' in self.components or not 'outputbox' in self.components: 1440 | return 1441 | member_name = self.searchbox.explored.name 1442 | if not member_name: 1443 | return 1444 | member_type = self.searchbox.explored.member_type 1445 | if 'argsbox' in self.components: 1446 | args = self.argsbox.content 1447 | else: 1448 | args = '' 1449 | try: 1450 | if AlarmInterrupt: 1451 | alarm(TIMEOUT) 1452 | if 'attribute' in member_type: 1453 | out = _eval_in_main("__obj__.{}" . format(member_name), locals={"__obj__": self.value}) 1454 | else: 1455 | out = _eval_in_main("__obj__.{}({})".format(member_name, args), locals={"__obj__": self.value}) 1456 | if AlarmInterrupt: 1457 | cancel_alarm() 1458 | except AlarmInterrupt: 1459 | self.outputbox.set_error("Timeout!") 1460 | return 1461 | except Exception as e: 1462 | if AlarmInterrupt: 1463 | cancel_alarm() 1464 | self.outputbox.set_error(e) 1465 | return 1466 | self.outputbox.set_output(out) 1467 | self.searchbox.set_display(member_name) # avoid any trailing '?' 1468 | if 'helpbox' in self.components: 1469 | self.helpbox.reset() # empty help box 1470 | def run_button(event): 1471 | if event['key'] == 'Enter': 1472 | compute_selected_member() 1473 | def submit_computation_noargs(w): 1474 | explored = self.searchbox.explored 1475 | if explored and explored.name and (not hasattr(explored, 'args') or not explored.args): 1476 | compute_selected_member() 1477 | def submit_computation(w): 1478 | compute_selected_member() 1479 | if 'searchbox' in self.components and 'argsbox' in self.components: 1480 | dlink((self.searchbox, 'explored'), (self.argsbox, 'explored')) 1481 | if 'searchbox' in self.components: 1482 | self.searchbox.on_submit(submit_computation_noargs) 1483 | if 'argsbox' in self.components: 1484 | self.argsbox.on_submit(submit_computation) 1485 | if 'runbutton' in self.components: 1486 | self.runbutton.on_click(compute_selected_member) 1487 | runbutton_enter_event = Event(source=self.runbutton, watched_events=['keyup']) 1488 | runbutton_enter_event.on_dom_event(run_button) 1489 | if 'outputbox' in self.components: 1490 | dlink((self.outputbox, 'value'), (self, 'value')) # Handle the clicks on output values 1491 | #def new_clicked_value(change): 1492 | # self.push_value(change.new) 1493 | #self.outputbox.observe(new_clicked_value, names='value') 1494 | enter_output_event = Event(source=self.outputbox, watched_events=['keyup']) 1495 | def enter_output(event): 1496 | if event['key'] == 'Enter' and self.outputbox.output.explorable: 1497 | self.value = self.outputbox.output.explorable 1498 | enter_output_event.on_dom_event(enter_output) # Enter-key triggered shortcut on all the output line 1499 | if 'searchbox' in self.components and 'helpbox' in self.components: 1500 | self.searchbox.set_help_target(self.helpbox) 1501 | def empty_helpbox(change): 1502 | self.helpbox.reset() 1503 | self.searchbox.observe(empty_helpbox, names='explored') 1504 | if 'codebox' in self.components: 1505 | def launch_evaluation(event): 1506 | if event['key'] == 'Enter' and (event['shiftKey'] or event['ctrlKey']): 1507 | locs = {"_": self.value, "__explorer__": self, "Hist": list(self._history)} 1508 | if self._history.initial_name: 1509 | locs[self._history.initial_name] = self._history[0] 1510 | self.codebox.evaluate(l=locs, o=self.outputbox, e=self.outputbox) 1511 | self.codebox.run_event.on_dom_event(launch_evaluation) 1512 | if self.test_mode: 1513 | self.donottrack = False 1514 | 1515 | def implement_settings_interactivity(self): 1516 | dlink((Settings,'_display_settings'), (self, '_display_settings')) 1517 | dlink((Settings, 'properties'), (self, '_properties_settings')) 1518 | @observe('_display_settings') 1519 | def display_settings_changed(self, change): 1520 | for c in self.components: 1521 | gettattr(self, c)._tooltip_visibility = change.new['show_tooltips'] 1522 | 1523 | def draw(self): 1524 | r""" 1525 | Setup Sage explorer visual display. 1526 | 1527 | TESTS:: 1528 | 1529 | sage: from sage_explorer import SageExplorer 1530 | sage: e = SageExplorer(42) 1531 | sage: e.create_components() 1532 | sage: e.implement_interactivity() 1533 | sage: e.draw() 1534 | sage: len(e.focuslist) 1535 | 10 1536 | """ 1537 | self.focuslist = [] # Will be used to allocate focus to successive components 1538 | self.focuslist.append(self.descriptionbox.children[1]) 1539 | propsvbox = VBox([self.descriptionbox, self.propsbox]) 1540 | for ec in self.propsbox.explorables: 1541 | self.focuslist.append(ec) 1542 | self.focuslist.append(self.visualbox) 1543 | propsvbox.add_class('explorer-flexitem') 1544 | topflex = HBox( 1545 | (propsvbox, Separator(' '), self.visualbox), 1546 | layout=Layout(margin='10px 0') 1547 | ) 1548 | topflex.add_class("explorer-flexrow") 1549 | top = VBox( 1550 | (self.titlebox, topflex) 1551 | ) 1552 | self.focuslist.append(self.histbox) 1553 | self.focuslist.append(self.searchbox) 1554 | self.focuslist.append(self.argsbox) 1555 | self.focuslist.append(self.runbutton) 1556 | middleflex = HBox([ 1557 | self.histbox, 1558 | Separator('.'), 1559 | self.searchbox, 1560 | Separator('('), 1561 | self.argsbox, 1562 | Separator(')'), 1563 | self.runbutton 1564 | ]) 1565 | middleflex.add_class("explorer-flexrow") 1566 | self.focuslist.append(self.codebox) 1567 | self.focuslist.append(self.outputbox.output) 1568 | self.focuslist.append(self.helpbox) 1569 | bottom = VBox([middleflex, self.codebox, self.outputbox, self.helpbox]) 1570 | self.children = (top, bottom) 1571 | self.distribute_focus() 1572 | 1573 | def distribute_focus(self): 1574 | for c in self.focuslist: 1575 | c.set_focusable(True) 1576 | 1577 | @observe('_history_index') 1578 | def history_selection(self, change): 1579 | if self.donottrack: 1580 | return 1581 | self.donottrack = True 1582 | self.value = self._history.get_item(change.new) 1583 | self.donottrack = False 1584 | 1585 | @observe('value') 1586 | def value_changed(self, change): 1587 | r""" 1588 | What to do when the value has been changed. 1589 | (Do not use this function if the value change 1590 | was made by a history selection). 1591 | 1592 | INPUT: 1593 | 1594 | - ``change`` -- a change Bunch 1595 | 1596 | TESTS:: 1597 | 1598 | sage: from sage_explorer import SageExplorer 1599 | sage: t = Tableau([[1, 2, 5, 6], [3], [4]]) 1600 | sage: new_t = Tableau([[1, 2, 7, 6], [3], [4]]) 1601 | sage: e = SageExplorer(t) 1602 | sage: e._history 1603 | ExplorableHistory([[[1, 2, 5, 6], [3], [4]]]) 1604 | sage: from traitlets import Bunch 1605 | sage: e.value_changed(Bunch({'name': 'value', 'old': t, 'new': new_t, 'owner': e, 'type': 'change'})) 1606 | sage: e._history 1607 | ExplorableHistory([[[1, 2, 5, 6], [3], [4]], [[1, 2, 7, 6], [3], [4]]]) 1608 | sage: e._history_index = int(0) 1609 | sage: e.value = 42 1610 | sage: e._history 1611 | ExplorableHistory([[[1, 2, 5, 6], [3], [4]], 42]) 1612 | sage: e._history_index 1613 | 1 1614 | sage: e._history_len 1615 | 2 1616 | """ 1617 | if self.donottrack: 1618 | return 1619 | old_val = change.old 1620 | new_val = change.new 1621 | actually_changed = (id(new_val) != id(old_val)) 1622 | if not actually_changed: 1623 | return 1624 | self.donottrack = True 1625 | need_to_cut = (self._history_len > self._history_index + 1) 1626 | if need_to_cut: # First click navigation after a history selection 1627 | shift = self._history_len - self._history_index - 1 1628 | self._history.pop(shift) 1629 | self._history.push(new_val) 1630 | self._history_len = len(self._history) 1631 | self._history_index += 1 1632 | self.reset() 1633 | self.donottrack = False 1634 | 1635 | def set_value(self, obj): 1636 | r""" 1637 | Set new math object `obj` to the explorer. 1638 | 1639 | TESTS:: 1640 | 1641 | sage: from sage_explorer.sage_explorer import SageExplorer 1642 | sage: from sage.combinat.partition import Partition 1643 | sage: p = Partition([3,3,2,1]) 1644 | sage: e = SageExplorer(p) 1645 | sage: e.get_value() 1646 | [3, 3, 2, 1] 1647 | sage: from sage.combinat.tableau import Tableau 1648 | sage: t = Tableau([[1,2,3,4], [5,6]]) 1649 | sage: e.set_value(t) 1650 | sage: e.get_value() 1651 | [[1, 2, 3, 4], [5, 6]] 1652 | """ 1653 | self.value = obj # If value has changed, will call the observer 1654 | 1655 | def get_value(self): 1656 | r""" 1657 | Return math object currently explored. 1658 | 1659 | TESTS:: 1660 | 1661 | sage: from sage_explorer.sage_explorer import SageExplorer 1662 | sage: from sage.combinat.partition import Partition 1663 | sage: p = Partition([3,3,2,1]) 1664 | sage: e = SageExplorer(p) 1665 | sage: e.get_value() 1666 | [3, 3, 2, 1] 1667 | """ 1668 | return self.value 1669 | 1670 | 1671 | class ExplorerSettings(HasTraits): 1672 | r""" 1673 | Explorer settings. Used as a singleton. 1674 | """ 1675 | show_tooltips = Bool(True) # Does the user actually want to see the explanatory tooltips? 1676 | _display_settings = Dict() # A dictionary for display settings 1677 | properties = Dict() # A dictionary of property -> list of context dictionaries 1678 | 1679 | def __init__(self, *args, **kwargs): 1680 | r""" 1681 | Init ExplorerSettings with optional argument `config`. 1682 | 1683 | TESTS:: 1684 | sage: from sage_explorer.sage_explorer import ExplorerSettings 1685 | sage: ES = ExplorerSettings() 1686 | sage: ES.show_tooltips 1687 | True 1688 | sage: type(ES.properties) 1689 | 1690 | sage: ES.properties['conjugate'] 1691 | [{'member of': Partitions}, {'member of': Tableaux}] 1692 | """ 1693 | super(HasTraits, self).__init__(*args, **kwargs) 1694 | if not 'config' in kwargs: 1695 | config = CONFIG_PROPERTIES 1696 | self.load_properties(config=config) 1697 | 1698 | @observe('show_tooltips') 1699 | def settings_changed(self, change): 1700 | self._display_settings = {'show_tooltips': change.new} 1701 | 1702 | def tooltips_visibility(self, visibility): 1703 | r""" 1704 | Switch tooltips visibility 1705 | """ 1706 | self.show_tooltips = visibility 1707 | 1708 | def load_properties(self, config=CONFIG_PROPERTIES): 1709 | r""" 1710 | Parse properties flat list 1711 | to make it a dictionary 1712 | property name -> list of contexts 1713 | 1714 | INPUT: 1715 | 1716 | - ``config`` -- a dictionary 'properties' -> list of dictionaries 1717 | 1718 | TESTS:: 1719 | sage: from sage_explorer.sage_explorer import ExplorerSettings 1720 | sage: ES = ExplorerSettings() 1721 | sage: ES.load_properties() 1722 | sage: len(ES.properties['addition_table']) 1723 | 1 1724 | sage: len(ES.properties['addition_table'][0]) 1725 | 2 1726 | sage: ES.properties['addition_table'][0]['when'](GF(7)) 1727 | True 1728 | sage: ES.properties['addition_table'][0]['when'](GF(29)) 1729 | False 1730 | sage: len(ES.properties['base_ring']) 1731 | 1 1732 | sage: ES.properties['base_ring'][0]['when'](CoxeterGroup(["A",2])) 1733 | False 1734 | sage: ES.properties['base_ring'][0]['when'](StandardTableaux(3).random_element()) 1735 | False 1736 | """ 1737 | self.properties = {} 1738 | for context in config['properties']: 1739 | propname = context['property'] 1740 | if propname not in self.properties: 1741 | self.properties[propname] = [] 1742 | new_context = {} 1743 | for key, val in context.items(): 1744 | if key == 'property': 1745 | continue 1746 | if key == 'label': 1747 | new_context[key] = val 1748 | elif key in ['instance of', 'not instance of', 'member of', 'not member of']: 1749 | new_context[key] = _eval_in_main(val) 1750 | elif key in ['when', 'not when']: 1751 | if callable(val): 1752 | new_context[key] = val 1753 | continue 1754 | first_part = val.split()[0] 1755 | func = None 1756 | try: 1757 | func = _eval_in_main(first_part) 1758 | except: 1759 | pass 1760 | if func is not None and val == first_part: 1761 | new_context[key] = func 1762 | continue 1763 | def build_test(key, val, first_part): 1764 | if func is not None: 1765 | return lambda obj:_eval_in_main(val)(obj) 1766 | remain = " " . join(val.split()[1:]) 1767 | def test_when(obj): 1768 | if not hasattr(obj, first_part): 1769 | return False 1770 | return hasattr(obj, first_part) and \ 1771 | _eval_in_main("{} {}" . format(str(getattr(obj, first_part).__call__()), remain)) 1772 | return test_when 1773 | new_context[key] = build_test(key, val, first_part) 1774 | self.properties[propname].append(new_context) 1775 | 1776 | def add_property(self, propname, instance_of=None, not_instance_of=None, member_of=None, not_member_of=None, 1777 | when=None, not_when=None, label=None): 1778 | r""" 1779 | Add/modify a context for `propname` for class `clsname` 1780 | in `properties` dictionary. 1781 | 1782 | INPUT: 1783 | 1784 | - ``propname`` -- a string 1785 | - ``instance_of`` -- a class 1786 | - ``not_instance_of`` -- a class 1787 | - ``member_of`` -- an object 1788 | - ``not_member_of`` -- an object 1789 | - ``when`` -- a method/function 1790 | - ``not_when`` -- a method/function 1791 | - ``label`` -- a string 1792 | 1793 | TESTS:: 1794 | sage: from sage_explorer.sage_explorer import ExplorerSettings 1795 | sage: ES = ExplorerSettings() 1796 | sage: ES.load_properties() 1797 | sage: ES.add_property('cardinality', instance_of=frozenset) 1798 | sage: ES.properties['cardinality'] 1799 | [{'member of': }, {'instance of': }] 1800 | sage: ES.add_property('cardinality', member_of=Groups().Finite()) 1801 | sage: len(ES.properties['cardinality']) 1802 | 3 1803 | sage: ES.add_property('__abs__') 1804 | sage: ES.properties['__abs__'] 1805 | [{}] 1806 | sage: ES.remove_property('__abs__') 1807 | sage: ES.properties['__abs__'] 1808 | [] 1809 | sage: ES.add_property('__abs__', when=lambda x:False) 1810 | sage: 'when' in ES.properties['__abs__'][0] 1811 | True 1812 | """ 1813 | properties = self.properties 1814 | if not propname in properties: 1815 | properties[propname] = [] 1816 | context = {} 1817 | if instance_of: 1818 | context['instance of'] = instance_of 1819 | if not_instance_of: 1820 | context['not instance of'] = not_instance_of 1821 | if member_of: 1822 | context['member of'] = member_of 1823 | if not_member_of: 1824 | context['not member of'] = not_member_of 1825 | if when: 1826 | context['when'] = when 1827 | if not_when: 1828 | context['not when'] = not_when 1829 | if label: 1830 | context['label'] = label 1831 | properties[propname].append(context) 1832 | 1833 | def remove_property(self, propname, instance_of=None, not_instance_of=None, member_of=None, not_member_of=None, 1834 | when=None, not_when=None, label=None): 1835 | r""" 1836 | Remove property in context defined by `clsname` and `predicate` 1837 | for `propname` in `properties` dictionary. 1838 | 1839 | INPUT: 1840 | 1841 | - ``propname`` -- a string 1842 | - ``instance_of`` -- a class 1843 | - ``not_instance_of`` -- a class 1844 | - ``member_of`` -- an object 1845 | - ``not_member_of`` -- an object 1846 | - ``when`` -- a method/function 1847 | - ``not_when`` -- a method/function 1848 | 1849 | TESTS:: 1850 | sage: from sage_explorer.sage_explorer import ExplorerSettings 1851 | sage: ES = ExplorerSettings() 1852 | sage: ES.load_properties() 1853 | sage: ES.add_property('cardinality', instance_of=frozenset) 1854 | sage: ES.properties['cardinality'] 1855 | [{'member of': }, 1856 | {'instance of': }] 1857 | sage: ES.remove_property('cardinality', instance_of=frozenset) 1858 | sage: ES.properties['cardinality'] 1859 | [{'member of': }] 1860 | sage: ES.remove_property('cardinality', member_of=EnumeratedSets.Finite) 1861 | sage: ES.properties['cardinality'] 1862 | [] 1863 | """ 1864 | properties = self.properties 1865 | if not propname in properties: 1866 | return 1867 | for context in properties[propname]: 1868 | found = True 1869 | if instance_of: 1870 | if 'instance of' not in context: 1871 | found = False 1872 | elif context['instance of'] != instance_of: 1873 | found = False 1874 | if not_instance_of: 1875 | if 'not instance of' not in context: 1876 | found = False 1877 | elif context['not instance of'] != not_instance_of: 1878 | found = False 1879 | if member_of: 1880 | if 'member of' not in context: 1881 | found = False 1882 | elif context['member of'] != member_of: 1883 | found = False 1884 | if not_member_of: 1885 | if 'not member of' not in context: 1886 | found = False 1887 | elif context['not member of'] != not_member_of: 1888 | found = False 1889 | if when: 1890 | if 'when' not in context: 1891 | found = False 1892 | elif context['when'] != when: 1893 | found = False 1894 | if not_when: 1895 | if 'not when' not in context: 1896 | found = False 1897 | elif context['not_when'] != not_when: 1898 | found = False 1899 | if found: 1900 | properties[propname].remove(context) 1901 | return 1902 | 1903 | Settings = ExplorerSettings() 1904 | SageExplorer.settings = Settings 1905 | --------------------------------------------------------------------------------