├── .gitignore ├── LICENSE.txt ├── README.rst ├── doc ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── islplot ├── __init__.py ├── blender.py ├── plotter.py ├── plotter3d.py └── support.py ├── notebooks ├── islplot-examples-3d.ipynb └── islplot-examples.ipynb ├── setup.py └── tests ├── README.txt ├── plotter.py └── support.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | tmp/ 4 | build/ 5 | dist/ 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # MIT license 2 | 3 | Copyright (c) 2014 Tobias Grosser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Copyright 2014 - `Tobias Grosser `_ 2 | 3 | islplot 4 | ======= 5 | 6 | islplot is a library that generates illustrations of integer sets 7 | and maps. It relies on `isl `_ to model the integer sets 8 | and uses the `islpy `_ Python bindings. 9 | Plotting is performed with `matplotlib `_. 10 | 11 | To get an impression of islplot have a look at the `2D examples `_ and `3D examples `_ (requires WebGL). 12 | 13 | The `documentation `_ is generated from the 14 | files in the doc/ directory by executing 'make html' (requires `sphinx 15 | `_). 16 | 17 | License: MIT (see `LICENSE.txt `_) 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /doc/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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/islplot.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/islplot.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/islplot" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/islplot" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # islplot documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Jan 4 18:35:53 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'islplot' 44 | copyright = u'2014, Tobias Grosser' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | #html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'islplotdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'islplot.tex', u'islplot Documentation', 187 | u'Tobias Grosser', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'islplot', u'islplot Documentation', 217 | [u'Tobias Grosser'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'islplot', u'islplot Documentation', 231 | u'Tobias Grosser', 'islplot', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | 244 | 245 | # Example configuration for intersphinx: refer to the Python standard library. 246 | intersphinx_mapping = {'http://docs.python.org/': None} 247 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. islplot documentation master file, created by 2 | sphinx-quickstart on Sat Jan 4 18:35:53 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to islplot's documentation! 7 | =================================== 8 | 9 | islplot is a Python module that allows to create graphical plots of `isl 10 | `_ sets and maps, which are of sets of 11 | integer points or relations between such sets that are bounded by piecewise 12 | quasi-affine constraints. They can be created and operated on in python using 13 | `islpy `_. 14 | 15 | It currently provides the following functions: 16 | 17 | .. automodule:: islplot.plotter 18 | :members: 19 | :undoc-members: 20 | 21 | .. automodule:: islplot.plotter3d 22 | :members: 23 | :undoc-members: 24 | 25 | .. automodule:: islplot.support 26 | :members: 27 | :undoc-members: 28 | 29 | Contents: 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | 42 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\islplot.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\islplot.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /islplot/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | islplot 3 | """ 4 | 5 | __all__ = ["plotter", "plotter3d", "support"] 6 | -------------------------------------------------------------------------------- /islplot/blender.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from islpy import * 3 | from islplot.support import * 4 | 5 | def make_material(name, color_diffuse, color_specular, alpha): 6 | """ 7 | Create a blender material. 8 | 9 | :param name: The name. 10 | :param color_diffuse: The diffuse color. 11 | :param color_specular: The specular color. 12 | :param alpha: The alpha channel. 13 | """ 14 | mat = bpy.data.materials.new(name) 15 | mat.diffuse_color = color_diffuse 16 | mat.diffuse_shader = 'LAMBERT' 17 | mat.diffuse_intensity = 1.0 18 | mat.specular_color = color_specular 19 | mat.specular_shader = 'COOKTORR' 20 | mat.specular_intensity = 0.5 21 | mat.alpha = alpha 22 | mat.ambient = 1 23 | mat.use_transparency = True 24 | return mat 25 | 26 | # Define a set of default colors 27 | red = make_material('Red', (1,0.1,0.1), (1,1,1), 1) 28 | green = make_material('Green', (0,1,0), (1,1,1), 1) 29 | blue = make_material('Blue', (0,0.3,1), (1,1,1), 1) 30 | white_trans = make_material('White', (1,1,1), (0.2,0.2,0.2), 0.4) 31 | white = make_material('White', (1,1,1), (1,1,1), 1) 32 | black = make_material('Black', (0,0,0), (1,1,1), 1) 33 | 34 | def remove_default_cube(): 35 | """ 36 | Remove the cube that is in the blender default scene to get an empty scene. 37 | """ 38 | bpy.data.objects["Cube"].select = True 39 | bpy.ops.object.delete() 40 | 41 | def set_lamp(): 42 | """ 43 | Set the position of the default lamp. 44 | """ 45 | bpy.data.objects["Lamp"].data.type = 'HEMI' 46 | bpy.data.objects["Lamp"].location = (20,20,20) 47 | 48 | def set_camera(location=(34,42,24), rotation=(1.08, 0.013, 2.43)): 49 | """ 50 | Set the location of the default camera. 51 | 52 | :param location: The camera's location. 53 | :param rotation: The camera's rotation. 54 | """ 55 | bpy.data.objects["Camera"].location = location 56 | bpy.data.objects["Camera"].rotation_euler = rotation 57 | 58 | def set_horizon(): 59 | """ 60 | Set the color of the horizon. 61 | """ 62 | bpy.context.scene.world.horizon_color = (1,1,1) 63 | 64 | def set_scene(): 65 | """ 66 | Prepare the scene for rendering. 67 | """ 68 | remove_default_cube() 69 | set_lamp() 70 | set_camera() 71 | set_horizon() 72 | bpy.context.scene.render.resolution_percentage = 60 73 | 74 | def save(filename): 75 | """ 76 | Save the current blender project. 77 | 78 | :param filename: The location where to save the file. 79 | """ 80 | bpy.ops.wm.save_as_mainfile(filepath=filename) 81 | 82 | def render(filename): 83 | """ 84 | Render the scene to a file. 85 | 86 | :param filename: The location where to store the rendered file. 87 | """ 88 | bpy.data.scenes['Scene'].render.filepath = filename 89 | bpy.ops.render.render( write_still=True ) 90 | 91 | def print_plane(height0=10, height1=10, color=white_trans, dim=0, units=True): 92 | """ 93 | Print a plane. 94 | 95 | :param color: The color of the plane. 96 | :param dim: The dimension controls the orientation and location of the plane. 97 | :param units: If units should be marked on the plane. 98 | :param height0: The width of the plane along the first direction. 99 | :param height1: The width of the plane along the second direction. 100 | """ 101 | dim1 = None 102 | if dim == 0: 103 | rotation=(0,0,0) 104 | dim1 = 1 105 | rotation1 = (0,1.5708,0) 106 | dim2 = 0 107 | rotation2 = (1.5708,0,0) 108 | resize=(height1, height0, 1) 109 | if dim == 1: 110 | rotation=(0,1.5708,0) 111 | dim1 = 2 112 | rotation1 = (1.5708,0,0) 113 | dim2 = 1 114 | rotation2 = (0,0,0) 115 | resize=(1, height1, height0) 116 | if dim == 2: 117 | rotation=(1.5708,0,0) 118 | dim1 = 0 119 | rotation1 = (0,0,0) 120 | dim2 = 2 121 | rotation2 = (0,1.5708,0) 122 | resize=(height0, 1, height1) 123 | 124 | bpy.ops.mesh.primitive_plane_add(location=(0,0,0), rotation=rotation) 125 | 126 | # FIXME: Re-enable resize 127 | # 128 | # This resize operation is necessary to actually form the planes. It 129 | # is disabled as this call currently fails with an invalid context 130 | # error when running directly in python with blender loaded as module. 131 | # This means we currently do not render those planes. 132 | # 133 | # bpy.ops.transform.resize(value=resize) 134 | 135 | 136 | ob = bpy.context.active_object 137 | ob.data.materials.append(color) 138 | 139 | if not units: 140 | return 141 | 142 | for i in range(-height0,height0, 1): 143 | if i == 0: 144 | continue 145 | 146 | location=[0,0,0] 147 | location[dim1] = i 148 | 149 | if i % 5 == 0: 150 | radius = 0.01 * 2 151 | else: 152 | radius = 0.01 153 | 154 | bpy.ops.mesh.primitive_cylinder_add(vertices=128, 155 | radius=radius, depth=2*height1, rotation=rotation1, 156 | location=location) 157 | ob = bpy.context.active_object 158 | ob.data.materials.append(black) 159 | 160 | for i in range(-height1,height1, 1): 161 | if i == 0: 162 | continue 163 | 164 | location=[0,0,0] 165 | location[dim2] = i 166 | 167 | if i % 5 == 0: 168 | radius = 0.01 * 2 169 | else: 170 | radius = 0.01 171 | 172 | bpy.ops.mesh.primitive_cylinder_add(vertices=128, 173 | radius=radius, depth=2*height0, rotation=rotation2, 174 | location=location) 175 | ob = bpy.context.active_object 176 | ob.data.materials.append(black) 177 | 178 | def print_axis(height, color, dim, unit_markers=True, labels=False): 179 | """ 180 | Print the axis of a coordinate system. 181 | 182 | :param height: The length of the axis. 183 | :param color: The color of the axis. 184 | :param dim: The dimension for which the axis is printed. 185 | :param unit_marks: If unit markers should be plotted. 186 | :param labels: The labels that sould be printed next to the axis 187 | """ 188 | if dim == 2: 189 | rotation = (0,0,0) 190 | if dim == 1: 191 | rotation = (-1.5708,0,0) 192 | if dim == 0: 193 | rotation = (0, 1.5708, 0) 194 | 195 | bpy.ops.mesh.primitive_cylinder_add(vertices=128, radius=0.1, 196 | depth=2 * (height+1), rotation=rotation, 197 | location=(0,0,0)) 198 | ob = bpy.context.active_object 199 | ob.data.materials.append(color) 200 | location = [0,0,0] 201 | location[dim] = height+1 202 | bpy.ops.mesh.primitive_cone_add(vertices=128, radius1=0.2, radius2=0, depth=1, 203 | rotation=rotation, location=location) 204 | ob = bpy.context.active_object 205 | ob.data.materials.append(color) 206 | top = ob 207 | 208 | if labels != False and (labels == True or labels[dim] != False): 209 | location=[0,0,0] 210 | if dim == 0: 211 | rotation = (1.5708,0, 2 * 1.5708) 212 | location[2] = 0.3 213 | location[1] = 0 214 | location[0] = height - 0.1 215 | if dim == 1: 216 | location[1] = height - 0.8 217 | location[2] = 0.3 218 | location[0] = 0 219 | rotation = (1.5708,0,1.5708) 220 | if dim == 2: 221 | rotation = (1.5708,0, 2 * 1.5708) 222 | location[2] = height - 1.2 223 | location[1] = 0 224 | location[0] = -0.5 225 | bpy.ops.object.text_add(enter_editmode=True, location = location, 226 | rotation=rotation) 227 | bpy.ops.font.delete() 228 | if labels[dim] == False: 229 | text = "i%d" % dim 230 | else: 231 | text = labels[dim] 232 | bpy.ops.font.text_insert(text=text) 233 | ob = bpy.context.active_object 234 | ob.data.size = 2 235 | ob.data.materials.append(color) 236 | 237 | if dim == 2: 238 | rotation = (-1.5708,0,0) 239 | rotation = (0,0,0) 240 | if dim == 1: 241 | rotation = (-1.5708,0,0) 242 | if dim == 0: 243 | rotation = (0, 1.5708, 0) 244 | 245 | if unit_markers: 246 | for i in range(-height,height+1, 1): 247 | location = [0, 0, 0] 248 | location[dim] = i 249 | if i % 5 == 0: 250 | depth = 2 * 0.05 251 | else: 252 | depth = 0.05 253 | bpy.ops.mesh.primitive_cylinder_add(vertices=128, 254 | radius=0.105, depth=depth, rotation=rotation, 255 | location=location) 256 | ob = bpy.context.active_object 257 | ob.data.materials.append(white) 258 | return top 259 | 260 | def add_coordinate_system(size=[10,10,10], print_planes=[True, False, False], 261 | unit_markers=True, labels=False): 262 | """ 263 | Plot a coordinate system. 264 | 265 | :param size: The size of the coordinate system along the different 266 | dimensions. 267 | :param print_plans: Either a single boolean value that enables or disables 268 | the printing of all planes or a vector of booleans that 269 | enables each plane individually. 270 | :param unit_markers: If unit markers should be printed on the plans and 271 | axis. 272 | """ 273 | axis = [] 274 | dim = 0 275 | a = print_axis(size[2], black, dim, unit_markers, labels) 276 | axis.append(a) 277 | dim = 1 278 | a = print_axis(size[1], black, dim, unit_markers, labels) 279 | axis.append(a) 280 | dim = 2 281 | a = print_axis(size[0], black, dim, unit_markers, labels) 282 | axis.append(a) 283 | 284 | if print_planes != False: 285 | if (print_planes == True or print_planes[0] == True): 286 | print_plane(size[1], size[2], dim=0) 287 | if (print_planes == True or print_planes[1] == True): 288 | print_plane(size[0], size[1], dim=1) 289 | if (print_planes == True or print_planes[2] == True): 290 | print_plane(size[2], size[0], dim=2) 291 | 292 | return axis 293 | 294 | 295 | def print_line(start, end): 296 | """ 297 | Print a line between two points. 298 | """ 299 | 300 | if not "islplot-tmp-line-a" in bpy.data.objects: 301 | """ 302 | We only construct a sphere once and then copy subsequent spheres from 303 | this one. This speeds up blender, as we avoid the additional checking 304 | normally performed by the bpy.ops.mesh.* functions. 305 | """ 306 | bpy.ops.mesh.primitive_uv_sphere_add(segments=1, ring_count=1, 307 | size=0.01, view_align=False, enter_editmode=False, 308 | location=(0,0,0), rotation=(0,0,0), layers=(True, False, 309 | False, False, False, False, False, False, False, 310 | False, False, False, False, False, False, 311 | False, False, False, False, False)) 312 | A = bpy.context.active_object 313 | A.name = "islplot-tmp-line-a" 314 | else: 315 | A = bpy.data.objects["islplot-tmp-line-a"] 316 | 317 | if not "islplot-tmp-line-b" in bpy.data.objects: 318 | """ 319 | We only construct a sphere once and then copy subsequent spheres from 320 | this one. This speeds up blender, as we avoid the additional checking 321 | normally performed by the bpy.ops.mesh.* functions. 322 | """ 323 | bpy.ops.mesh.primitive_uv_sphere_add(segments=1, ring_count=1, 324 | size=0.01, view_align=False, enter_editmode=False, 325 | location=(0,0,0), rotation=(0,0,0), layers=(True, False, 326 | False, False, False, False, False, False, False, 327 | False, False, False, False, False, False, 328 | False, False, False, False, False)) 329 | B = bpy.context.active_object 330 | B.name = "islplot-tmp-line-b" 331 | else: 332 | B = bpy.data.objects["islplot-tmp-line-b"] 333 | 334 | A.location = start 335 | B.location = end 336 | 337 | l = [A,B] 338 | draw_curve = bpy.data.curves.new('draw_curve','CURVE') 339 | draw_curve.dimensions = '3D' 340 | spline = draw_curve.splines.new('BEZIER') 341 | spline.bezier_points.add(len(l)-1) 342 | curve = bpy.data.objects.new('curve',draw_curve) 343 | bpy.context.scene.objects.link(curve) 344 | 345 | # Curve settings for new curve 346 | draw_curve.resolution_u = 64 347 | draw_curve.fill_mode = 'FULL' 348 | draw_curve.bevel_depth = 0.02 349 | draw_curve.bevel_resolution = 0.02 350 | 351 | # Assign bezier points to selection object locations 352 | for i in range(len(l)): 353 | p = spline.bezier_points[i] 354 | p.co = l[i].location 355 | p.handle_right_type="VECTOR" 356 | p.handle_left_type="VECTOR" 357 | 358 | curve.data.materials.append(black) 359 | 360 | def print_face_borders(vertices, faces): 361 | """ 362 | Print lines along the edges of a set of faces. 363 | 364 | :param faces: The faces for which to print the edges. 365 | :param vertices: The locations of the vertices. 366 | """ 367 | for face in faces: 368 | for i in range(len(face)): 369 | a = vertices[face[i]] 370 | b = vertices[face[(i+1)%len(face)]] 371 | print_line(a, b) 372 | 373 | def print_sphere(location): 374 | """ 375 | Print a sphere at a given location. 376 | 377 | :param location: The location of the sphere. 378 | 379 | """ 380 | 381 | if not "islplot-tmp-sphere" in bpy.data.objects: 382 | """ 383 | We only construct a sphere once and then copy subsequent spheres from 384 | this one. This speeds up blender, as we avoid the additional checking 385 | normally performed by the bpy.ops.mesh.* functions. 386 | """ 387 | bpy.ops.mesh.primitive_uv_sphere_add(segments=8, ring_count=8, 388 | size=0.1, view_align=False, enter_editmode=False, 389 | location=(0,0,0), rotation=(0,0,0), layers=(True, False, 390 | False, False, False, False, False, False, False, 391 | False, False, False, False, False, False, 392 | False, False, False, False, False)) 393 | sphere = bpy.context.active_object 394 | sphere.name = "islplot-tmp-sphere" 395 | sphere.select = False 396 | sphere.data.materials.append(black) 397 | else: 398 | sphere = bpy.data.objects["islplot-tmp-sphere"] 399 | 400 | l = location 401 | ob = sphere.copy() 402 | ob.name = "Sphere (%d, %d, %d)" % (l[0], l[1], l[2]) 403 | ob.location = l 404 | ob.data = sphere.data.copy() 405 | bpy.context.scene.objects.link(ob) 406 | return ob 407 | 408 | def plot_bset_shape(bset_data, name, material, borders=True): 409 | """ 410 | Given an basic set, plot the shape formed by the constraints that define 411 | the basic set. 412 | 413 | :param bset_data: The basic set to plot. 414 | :param name: The name the resulting mesh should have. 415 | :param material: The material to use for the faces. 416 | :param borders: Printer bordes of the shape. 417 | """ 418 | vertices, faces = get_vertices_and_faces(bset_data) 419 | if borders: 420 | print_face_borders(vertices, faces) 421 | bpy.ops.object.add(type='MESH') 422 | ob = bpy.context.object 423 | ob.name = name 424 | me = ob.data 425 | me.from_pydata(vertices, [], faces) 426 | me.materials.append(material) 427 | me.update() 428 | ob.location[0] = 0 429 | ob.location[1] = 0 430 | ob.location[2] = 0 431 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') 432 | return ob 433 | 434 | def plot_set_points(set_data): 435 | points = bset_get_points(set_data, only_hull=True) 436 | for point in points: 437 | s = print_sphere(point) 438 | 439 | def plot_bset(bset_data, color, name, add_spheres=True, borders=True): 440 | tile = plot_bset_shape(bset_data, name, color, borders) 441 | 442 | if add_spheres: 443 | plot_set_points(bset_data) 444 | bpy.context.scene.update() 445 | return tile 446 | 447 | def plot_all(schedule, dimensions_to_visualize, add_spheres=False, borders=True, 448 | get_color=None): 449 | """ 450 | Given a schedule, we print the individual tiles. 451 | 452 | TODO: This is just a quick hack. This code should be shared between the 453 | different renderers. 454 | 455 | :param schedule: The schedule to visualize. 456 | :parma dimensions_to_visualize: The map used to define the dimensions that 457 | will be visualized. 458 | :param add_spheres: Print the individual elements in the set. 459 | :param getColor: A function that takes a tile id and returns the 460 | corresponding tile color. 461 | 462 | 463 | """ 464 | tileIDSet = schedule.range() 465 | 466 | tileIDs = [] 467 | tileIDSet.foreach_point(tileIDs.append) 468 | tileIDs = sort_points(tileIDs) 469 | 470 | for tileID in tileIDs: 471 | tileIDSet = Set.from_point(tileID) 472 | tileSet = schedule.intersect_range(tileIDSet).domain() 473 | tileSet = tileSet.apply(dimensions_to_visualize) 474 | assert tileSet == tileSet.convex_hull() 475 | tileSet = tileSet.convex_hull() 476 | 477 | color = get_color(tileID) 478 | name = "Tile " + str(get_point_coordinates(tileID)) 479 | plot_bset(tileSet, color, name, add_spheres, borders) 480 | -------------------------------------------------------------------------------- /islplot/plotter.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as _plt 2 | import islpy as _islpy 3 | from islplot.support import * 4 | 5 | def plot_set_points(set_data, color="black", size=10, marker="o", scale=1): 6 | """ 7 | Plot the individual points of a two dimensional isl set. 8 | 9 | :param set_data: The islpy.Set to plot. 10 | :param color: The color of the points. 11 | :param size: The diameter of the points. 12 | :param marker: The marker used to mark a point. 13 | :param scale: Scale the values. 14 | """ 15 | points = [] 16 | points = bset_get_points(set_data, scale=scale) 17 | dimX = [x[0] for x in points] 18 | dimY = [x[1] for x in points] 19 | _plt.plot(dimX, dimY, marker, markersize=size, color=color, lw=0) 20 | 21 | def _plot_arrow(start, end, graph, *args, **kwargs): 22 | """ 23 | Plot an arrow from start to end. 24 | 25 | :param start: The start position. 26 | :param end: The end position. 27 | :param style: The line style to use (default is "->"). 28 | :param width: The width of the line. 29 | :param color: The color of the line. 30 | """ 31 | style = kwargs.pop("style", "->") 32 | width = kwargs.pop("width", 1) 33 | color = kwargs.pop("color", "black") 34 | shrink = kwargs.pop("shrink", 10) 35 | 36 | if (start == end): 37 | a,b = start 38 | graph.annotate("", xy=(a, b+.15), xycoords='data', 39 | xytext=start, textcoords='data', 40 | arrowprops=dict(arrowstyle=style, 41 | connectionstyle="arc3", 42 | shrinkA=0, 43 | shrinkB=0, 44 | linewidth=width, 45 | color=color) 46 | ) 47 | return 48 | 49 | graph.annotate("", xy=end, xycoords='data', 50 | xytext=start, textcoords='data', 51 | arrowprops=dict(arrowstyle=style, 52 | connectionstyle="arc", 53 | shrinkA=shrink, 54 | shrinkB=shrink, 55 | linewidth=width, 56 | color=color) 57 | ) 58 | 59 | def plot_map(map_data, edge_style="->", edge_width=1, color="black", shrink=10, 60 | scale = 1): 61 | """ 62 | Given a map from a two dimensional set to another two dimensional set this 63 | functions prints the relations in this map as arrows going from the input 64 | to the output element. 65 | 66 | :param map_data: The islpy.Map to plot. 67 | :param color: The color of the arrows. 68 | :param edge_style: The style used to plot the arrows. 69 | :param edge_width: The width used to plot the arrows. 70 | :param shrink: The distance before around the start/end which is not plotted 71 | to. 72 | :param scale: Scale the values. 73 | """ 74 | start_points = [] 75 | 76 | map_data.range().foreach_point(start_points.append) 77 | 78 | for start in start_points: 79 | end_points = [] 80 | limited = map_data.intersect_range(_islpy.BasicSet.from_point(start)) 81 | limited.domain().foreach_point(end_points.append) 82 | for end in end_points: 83 | _plot_arrow(get_point_coordinates(end, scale), 84 | get_point_coordinates(start, scale), 85 | _plt, color=color, style=edge_style, 86 | width=edge_width, shrink=shrink) 87 | 88 | def plot_bset_shape(bset_data, show_vertices=True, color="gray", 89 | alpha=1.0, 90 | vertex_color=None, 91 | vertex_marker="o", vertex_size=10, 92 | scale=1, border=0): 93 | """ 94 | Given an basic set, plot the shape formed by the constraints that define 95 | the basic set. 96 | 97 | :param bset_data: The basic set to plot. 98 | :param show_vertices: Show the vertices at the corners of the basic set's 99 | shape. 100 | :param color: The background color of the shape. 101 | :param alpha: The alpha value to use for the shape. 102 | :param vertex_color: The color of the vertex markers. 103 | :param vertex_marker: The marker used to draw the vertices. 104 | :param vertex_size: The size of the vertices. 105 | :param border: Increase the size of the area filled with the background 106 | by the value given as 'border'. 107 | :param scale: Scale the values. 108 | """ 109 | 110 | assert bset_data.is_bounded(), "Expected bounded set" 111 | 112 | if not vertex_color: 113 | vertex_color = color 114 | 115 | vertices = bset_get_vertex_coordinates(bset_data, scale=scale) 116 | 117 | if show_vertices: 118 | dimX = [x[0] for x in vertices] 119 | dimY = [x[1] for x in vertices] 120 | _plt.plot(dimX, dimY, vertex_marker, markersize=vertex_size, 121 | color=vertex_color) 122 | 123 | if len(vertices) == 0: 124 | return 125 | 126 | import matplotlib.path as _matplotlib_path 127 | import matplotlib.patches as _matplotlib_patches 128 | Path = _matplotlib_path.Path 129 | PathPatch = _matplotlib_patches.PathPatch 130 | codes = [Path.LINETO] * len(vertices) 131 | codes[0] = Path.MOVETO 132 | pathdata = [(code, tuple(coord)) for code, coord in zip(codes, vertices)] 133 | pathdata.append((Path.CLOSEPOLY, (0, 0))) 134 | codes, verts = zip(*pathdata) 135 | import matplotlib.transforms as _matplotlib_transforms 136 | t = _matplotlib_transforms.Affine2D().translate(1, 0) 137 | path = Path(verts, codes) 138 | 139 | linewidth = 0 140 | fill = True 141 | 142 | if len(vertices) == 2: 143 | linewidth = 2; 144 | fill = False; 145 | 146 | pathes = [] 147 | import math 148 | steps = 200 149 | for i in range(steps): 150 | pi = i * 2 * math.pi/steps 151 | offset = border 152 | x = math.sin(pi) * offset 153 | y = math.cos(pi) * offset 154 | t = _matplotlib_transforms.Affine2D().translate(x,y) 155 | pathT = path.transformed(t) 156 | pathes.append(pathT) 157 | 158 | for p in pathes: 159 | path = _matplotlib_path.Path.make_compound_path(path, p) 160 | 161 | if len(vertices) == 1: 162 | patch = _matplotlib_patches.Circle(vertices[0], border, color=color, 163 | alpha=alpha) 164 | else: 165 | patch = PathPatch(path, alpha=alpha, linewidth=linewidth, 166 | color=color, fill=fill) 167 | _plt.gca().add_patch(patch) 168 | 169 | def plot_set_shapes(set_data, *args, **kwargs): 170 | """ 171 | Plot a set of concex shapes for the individual basic sets this set consists 172 | of. 173 | 174 | :param set_data: The set to plot. 175 | """ 176 | 177 | assert set_data.is_bounded(), "Expected bounded set" 178 | 179 | set_data.foreach_basic_set(lambda x: plot_bset_shape(x, **kwargs)) 180 | 181 | 182 | def plot_map_as_groups(bmap, color="gray", alpha=1.0, 183 | vertex_color=None, vertex_marker="o", 184 | vertex_size=10, scale=1, border=0.25): 185 | """ 186 | Plot a map in groups of convex sets 187 | 188 | This function expects a map that assigns each domain element a single 189 | group id, such that each group forms a convex set of points. This function 190 | plots now each group as such a convex shape. 191 | 192 | This is e.g. useful to illustrate a tiling that is given as a between 193 | iteration vectors to tile ids. 194 | 195 | :param bmap: The map defining the groups of convex sets. 196 | :param vertex_color: The color the vertices are plotted. 197 | :param vertex_size: The size the vertices are plotted. 198 | :param vertex_marker: The marker the vertices are plotted as. 199 | :param color: The color the shapes are plotted. 200 | :param border: Increase the size of the area filled with the background 201 | by the value given as 'border'. 202 | :param alpha: The alpha the shapes are plotted. 203 | """ 204 | 205 | if not vertex_color: 206 | vertex_color = color 207 | 208 | points = [] 209 | range = bmap.range() 210 | range.foreach_point(points.append) 211 | 212 | for point in points: 213 | point_set = _islpy.BasicSet.from_point(point) 214 | part_set = bmap.intersect_range(point_set).domain() 215 | part_set_convex = part_set.convex_hull() 216 | 217 | # We currently expect that each group can be represented by a 218 | # single convex set. 219 | assert (part_set == part_set_convex) 220 | 221 | part_set = part_set_convex 222 | 223 | plot_set_points(part_set, color=vertex_color, size=vertex_size, 224 | marker=vertex_marker, scale=scale) 225 | part_set = part_set.remove_divs() 226 | plot_bset_shape(part_set, color=color, alpha=alpha, 227 | vertex_color=vertex_color, 228 | vertex_size=vertex_size, vertex_marker=vertex_marker, 229 | show_vertices=False, scale=scale, border=border) 230 | 231 | def plot_domain(domain, dependences=None, tiling=None, space=None, 232 | tile_color="blue", tile_alpha=1, 233 | vertex_color = "black", vertex_size=10, 234 | vertex_marker="o", background=True, 235 | bg_vertex_color = "lightgray", bg_vertex_size=10, 236 | bg_vertex_marker="o", 237 | dep_color="gray", dep_style="->", dep_width=1, 238 | shrink=10, border=0.25 239 | ): 240 | """ 241 | Plot an iteration space domain and related information. 242 | 243 | 244 | :param domain: The domain of the iteration space 245 | :param dependences: The dependences between the different iterations 246 | :param tiling: A mapping from iteration space groups onto their corresponding 247 | (possibly multi-dimensional) tile ID. 248 | :param space: Show the data after mapping it to a new space. 249 | :param tile_color: The color to use for the tile shape. 250 | :param tile_alpha: The alpha value used for the tile background. 251 | :param vertex_color: The color of the vertex markers. 252 | :param vertex_marker: The marker used to draw the vertices. 253 | :param vertex_size: The size of the vertices. 254 | :param background: If a background should be printed. 255 | :param bg_vertex_color: The color of the vertex markers. 256 | :param bg_vertex_marker: The marker used to draw the vertices. 257 | :param bg_vertex_size: The size of the vertices. 258 | :param dep_color: The color used to plot the dependency arrows. 259 | :param dep_style: The style used to plot the dependency arrows. 260 | :param dep_width: The width used to plot the dependency arrows. 261 | :param shrink: The distance before around the start/end of the dependences 262 | around which is not plotted. 263 | :param border: Increase the size of the area filled with the background 264 | by the value given as 'border'. 265 | """ 266 | 267 | if space: 268 | domain = domain.apply(space) 269 | if dependences: 270 | dependences = dependences.apply_range(space) 271 | dependences = dependences.apply_domain(space) 272 | if tiling: 273 | tiling = tiling.apply_domain(space) 274 | 275 | 276 | if background: 277 | hull = get_rectangular_hull(domain, 1) 278 | plot_set_points(hull, color=bg_vertex_color, size=bg_vertex_size, 279 | marker=bg_vertex_marker) 280 | 281 | plot_set_points(domain, color=vertex_color, size=vertex_size, 282 | marker=vertex_marker) 283 | 284 | if dependences: 285 | dependences = dependences.intersect_range(domain) 286 | dependences = dependences.intersect_domain(domain) 287 | if tiling: 288 | same_tile = tiling.apply_range(tiling.reverse()) 289 | dependences = dependences.subtract(same_tile) 290 | plot_map(dependences, color=dep_color, edge_style=dep_style, 291 | edge_width=dep_width, shrink=shrink) 292 | 293 | if tiling: 294 | tiling = tiling.intersect_domain(domain) 295 | plot_map_as_groups(tiling, color=tile_color, vertex_color=vertex_color, 296 | vertex_size=vertex_size, vertex_marker=vertex_marker, 297 | alpha=tile_alpha, border=border) 298 | 299 | __all__ = ['plot_set_points', 'plot_bset_shape', 'plot_set_shapes', 300 | 'plot_map', 'plot_map_as_groups', 'plot_domain'] 301 | -------------------------------------------------------------------------------- /islplot/support.py: -------------------------------------------------------------------------------- 1 | import islpy as _islpy 2 | from islpy import * 3 | 4 | def get_point_coordinates(point, scale=1): 5 | result = [] 6 | for i in range(point.space.dim(_islpy.dim_type.set)): 7 | result.append(int(point.get_coordinate_val(_islpy.dim_type.set, i) 8 | .get_num_si())/ scale) 9 | 10 | return result 11 | 12 | def _vertex_to_rational_point(vertex): 13 | """ 14 | Given an n-dimensional vertex, this function returns an n-tuple consisting 15 | of pairs of integers. Each pair represents the rational value of the 16 | specific dimension with the first element of the pair being the nominator 17 | and the second element being the denominator. 18 | """ 19 | expr = vertex.get_expr() 20 | 21 | value = [] 22 | 23 | for i in range(expr.dim(dim_type.out)): 24 | subexpr = expr.get_aff(i) 25 | val = subexpr.get_constant_val() 26 | value.append((val.get_num_si(), val.get_den_val().to_python())) 27 | 28 | return value 29 | 30 | def _vertex_get_coordinates(vertex, scale=1): 31 | """ 32 | Get the coordinates of the an isl vertex as a tuple of float values. 33 | 34 | To extract the coordinates we first get the expression defining the vertex. 35 | This expression is given as a rational set that specifies its (possibly 36 | rational) coordinates. We then convert this set into the tuple we will 37 | return. 38 | 39 | Example: 40 | 41 | For a vertex defined by the rational set 42 | { rat: S[i, j] : 2i = 7 and 2j = 9 } we produce the output (7/2, 9/2). 43 | 44 | :param vertex: The vertex from which we extract the coordinates. 45 | """ 46 | r = _vertex_to_rational_point(vertex) 47 | return [(1.0 * x[0] / x[1])/scale for x in r] 48 | 49 | def _is_vertex_on_constraint(vertex, constraint): 50 | """ 51 | Given a vertex and a constraint, check if the vertex is on the plane defined 52 | by the constraint. For inequality constraints, the plane we look at is the 53 | extremal plane that separates the elements that fulfill an inequality 54 | constraint from the elements that do not fulfill this constraints. 55 | """ 56 | r = _vertex_to_rational_point(vertex) 57 | 58 | dims = constraint.space.dim(dim_type.set) 59 | v = [] 60 | for d in range(dims): 61 | v.append(constraint.get_coefficient_val(dim_type.set, d).get_num_si()) 62 | 63 | summ = 0 64 | 65 | import numpy 66 | for i in range(dims): 67 | prod = 1 68 | for j in range(dims): 69 | if i == j: 70 | prod *= r[j][0] 71 | else: 72 | prod *= r[j][1] 73 | summ += v[i] * prod 74 | 75 | constant = constraint.get_constant_val().get_num_si() 76 | summ += numpy.product([x[1] for x in r]) * constant 77 | 78 | return int(summ) == 0 79 | 80 | 81 | def bset_get_vertex_coordinates(bset_data, scale=1): 82 | """ 83 | Given a basic set return the list of vertices at the corners. 84 | 85 | :param bset_data: The basic set to get the vertices from 86 | """ 87 | 88 | # Get the vertices. 89 | vertices = [] 90 | bset_data.compute_vertices().foreach_vertex(vertices.append) 91 | f = lambda x: _vertex_get_coordinates(x, scale) 92 | vertices = list(map(f, vertices)) 93 | 94 | if len(vertices) <= 1: 95 | return vertices 96 | 97 | # Sort the vertices in clockwise order. 98 | # 99 | # We select a 'center' point that lies within the convex hull of the 100 | # vertices. We then sort all points according to the direction (given as an 101 | # angle in radiens) they lie in respect to the center point. 102 | from math import atan2 as _atan2 103 | center = ((vertices[0][0] + vertices[1][0]) / 2.0, 104 | (vertices[0][1] + vertices[1][1]) / 2.0) 105 | f = lambda x: _atan2(x[0]-center[0], x[1]-center[1]) 106 | vertices = sorted(vertices, key=f) 107 | return vertices 108 | 109 | 110 | from math import sqrt 111 | from math import degrees 112 | from math import acos 113 | 114 | def cross(a, b): 115 | c = [a[1]*b[2] - a[2]*b[1], 116 | a[2]*b[0] - a[0]*b[2], 117 | a[0]*b[1] - a[1]*b[0]] 118 | 119 | return c 120 | 121 | def sub(A,B): 122 | return [A[0]-B[0], A[1]-B[1], A[2]-B[2]] 123 | 124 | def norm(A,B,C): 125 | return(cross(sub(A,C),sub(B,C))) 126 | 127 | def dotProduct(A,B): 128 | return A[0] * B[0] + A[1] * B[1] + A[2] * B[2] 129 | 130 | def magnitude(A): 131 | return sqrt(A[0]*A[0] + A[1]*A[1] + A[2]*A[2]) 132 | 133 | def formular(A,B): 134 | res = dotProduct(A,B) / (magnitude(A) * magnitude(B)) 135 | res = float(str(res)) 136 | # Due to rounding errors res may be smaller than one. We fix this here. 137 | res = max(-1, res) 138 | res = acos(res) 139 | res = degrees(res) 140 | return res 141 | 142 | def angle(Q,M,O,N): 143 | if Q == M: 144 | return 0 145 | OM = sub(M,O) 146 | OQ = sub(Q,O) 147 | 148 | sig = dotProduct(N,cross(OM, OQ)) 149 | 150 | if sig >= 0: 151 | return formular(OQ,OM) 152 | else: 153 | return -formular(OQ,OM) 154 | 155 | def get_vertices_for_constraint(vertices, constraint): 156 | """ 157 | Return the list of vertices within a hyperspace. 158 | 159 | Given a constraint and a list of vertices, we filter the list of vertices 160 | such that only the vertices that are on the plane defined by the constraint 161 | are returned. We then sort the vertices such that the order defines a 162 | convex shape. 163 | """ 164 | points = [] 165 | for v in vertices: 166 | if _is_vertex_on_constraint(v, constraint): 167 | points.append(_vertex_get_coordinates(v)) 168 | 169 | if len(points) == 0: 170 | return None 171 | 172 | points.sort() 173 | import itertools 174 | points = list(points for points,_ in itertools.groupby(points)) 175 | 176 | A = points[0] 177 | if len(points) == 1: 178 | return [A] 179 | B = points[1] 180 | if len(points) == 2: 181 | return [A, B] 182 | C = points[2] 183 | N = norm(A,B,C) 184 | center = [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2, (A[2] + B[2]) / 2] 185 | f = lambda a: angle(A, a, center, N) 186 | points = sorted(points, key=f) 187 | return points 188 | 189 | def isSubset(parent, child): 190 | if len(parent) <= len(child): 191 | return False 192 | for c in child: 193 | contained = False 194 | for p in parent: 195 | if p == c: 196 | contained = True 197 | break 198 | 199 | if not contained: 200 | return False 201 | 202 | return True 203 | 204 | def bset_get_faces(basicSet): 205 | """ 206 | Get a list of faces from a basic set 207 | 208 | Given a basic set we return a list of faces, where each face is represented 209 | by a list of restricting vertices. The list of vertices is sorted in 210 | clockwise (or counterclockwise) order around the center of the face. 211 | Vertices may have rational coordinates. A vertice is represented as a three 212 | tuple. 213 | """ 214 | faces = [] 215 | vertices = [] 216 | basicSet.compute_vertices().foreach_vertex(vertices.append) 217 | f = lambda c: faces.append(get_vertices_for_constraint(vertices, c)) 218 | basicSet.foreach_constraint(f) 219 | 220 | # Remove empty elements, duplicates and subset of elements 221 | faces = filter(lambda x: not x == None, faces) 222 | faces = list(faces) 223 | faces = [x for x in faces if not 224 | any(isSubset(y, x) for y in faces if x is not y)] 225 | faces.sort() 226 | import itertools 227 | faces = list(faces for faces,_ in itertools.groupby(faces)) 228 | return faces 229 | 230 | def set_get_faces(set_data): 231 | """ 232 | Get a list of faces from a set 233 | 234 | Given a basic set we return a list of faces, where each face is represented 235 | by a list of restricting vertices. The list of vertices is sorted in 236 | clockwise (or counterclockwise) order around the center of the face. 237 | Vertices may have rational coordinates. A vertice is represented as a three 238 | tuple. 239 | """ 240 | 241 | bsets = [] 242 | f = lambda x: bsets.append(x) 243 | set_data.foreach_basic_set(f) 244 | return list(map(bset_get_faces, bsets)) 245 | 246 | 247 | def make_tuple(vertex): 248 | return (vertex[0], vertex[1], vertex[2]) 249 | 250 | def get_vertex_to_index_map(vertexlist): 251 | res = {} 252 | i = 0 253 | for v in vertexlist: 254 | res[make_tuple(v)] = i 255 | i += 1 256 | return res 257 | 258 | def translate_faces_to_indexes(faces, vertexmap): 259 | """ 260 | Given a list of faces, translate the vertex defining it from their explit 261 | offsets to their index as provided by the vertexmap, a mapping from vertices 262 | to vertex indices. 263 | """ 264 | new_faces = [] 265 | for face in faces: 266 | new_face = [] 267 | for vertex in face: 268 | new_face.append(vertexmap[make_tuple(vertex)]) 269 | new_faces.append(new_face) 270 | return new_faces 271 | 272 | def get_vertices_and_faces(set_data): 273 | """ 274 | Given an isl set, return a tuple that contains the vertices and faces of 275 | this set. The vertices are sorted in lexicographic order. In the faces, 276 | the vertices are referenced by their position within the vertex list. The 277 | vertices of a face are sorted such that connecting subsequent vertices 278 | yields a convex form. 279 | """ 280 | data = set_get_faces(set_data) 281 | if len(data) == 0: 282 | return ([], []) 283 | 284 | faces = data[0] 285 | vertices = [vertex for face in faces for vertex in face] 286 | vertices.sort() 287 | import itertools 288 | vertices = list(vertices for vertices, _ in itertools.groupby(vertices)) 289 | vertexmap = get_vertex_to_index_map(vertices) 290 | 291 | faces = translate_faces_to_indexes(faces, vertexmap) 292 | return (vertices, faces) 293 | 294 | def _constraint_make_equality_set(x): 295 | e = Constraint.equality_alloc(x.get_local_space()) 296 | e = e.set_constant_val(x.get_constant_val().get_num_si()) 297 | 298 | for i in range(x.space.dim(dim_type.set)): 299 | e = e.set_coefficient_val(dim_type.set, i, 300 | x.get_coefficient_val(dim_type.set, i).get_num_si()) 301 | for i in range(x.space.dim(dim_type.param)): 302 | e = e.set_coefficient_val(dim_type.param, i, 303 | x.get_coefficient_val(dim_type.param, i).get_num_si()) 304 | 305 | return BasicSet.universe(x.space).add_constraint(e) 306 | 307 | def bset_get_points(bset_data, only_hull=False, scale=1): 308 | """ 309 | Given a basic set return the points within this set 310 | 311 | :param bset_data: The set that contains the points. 312 | :param only_hull: Only return the point that are on the hull of the set. 313 | :param scale: Scale the values. 314 | """ 315 | 316 | if only_hull: 317 | hull = [None] 318 | hull[0] = Set.empty(bset_data.space) 319 | def add(c): 320 | const_eq = _constraint_make_equality_set(c) 321 | const_eq = const_eq.intersect(bset_data) 322 | hull[0] = hull[0].union(const_eq) 323 | bset_data.foreach_constraint(add) 324 | bset_data = hull[0] 325 | 326 | points = [] 327 | f = lambda x: points.append(get_point_coordinates(x, scale)) 328 | bset_data.foreach_point(f) 329 | points = sorted(points) 330 | return points 331 | 332 | def get_rectangular_hull(set_data, offset=0): 333 | uset_data = Set.universe(set_data.get_space()) 334 | 335 | for dim in range(0,2): 336 | ls = LocalSpace.from_space(set_data.get_space()) 337 | c = Constraint.inequality_alloc(ls) 338 | incr = Map("{{[i]->[i+{0}]}}".format(offset)) 339 | decr = Map("{{[i]->[i-{0}]}}".format(offset)) 340 | 341 | dim_val = Aff.zero_on_domain(ls).set_coefficient_val(dim_type.in_, dim, 342 | 1) 343 | dim_val = PwAff.from_aff(dim_val) 344 | dim_val = Map.from_pw_aff(dim_val) 345 | 346 | space = dim_val.get_space() 347 | dim_cst = Map.universe(space) 348 | max_set = Set.from_pw_aff(set_data.dim_max(dim)) 349 | dim_cst = dim_cst.intersect_range(max_set) 350 | dim_cst = dim_cst.apply_range(incr) 351 | 352 | diff = Map.lex_le_map(dim_val, dim_cst) 353 | uset_data = uset_data.intersect(diff.domain()) 354 | 355 | dim_cst = Map.universe(space) 356 | min_set = Set.from_pw_aff(set_data.dim_min(dim)) 357 | dim_cst = dim_cst.intersect_range(min_set) 358 | dim_cst = dim_cst.apply_range(decr) 359 | 360 | diff = Map.lex_ge_map(dim_val, dim_cst) 361 | uset_data = uset_data.intersect(diff.domain()) 362 | 363 | return uset_data 364 | 365 | def cmp_points(a, b): 366 | a = Set.from_point(a) 367 | b = Set.from_point(b) 368 | if a.lex_le_set(b).is_empty(): 369 | return 1 370 | else: 371 | return -1 372 | 373 | def cmp_to_key(mycmp): 374 | 'Convert a cmp= function into a key= function' 375 | class Key(object): 376 | def __init__(self, obj, *args): 377 | self.obj = obj 378 | def __lt__(self, other): 379 | return mycmp(self.obj, other.obj) < 0 380 | def __gt__(self, other): 381 | return mycmp(self.obj, other.obj) > 0 382 | def __eq__(self, other): 383 | return mycmp(self.obj, other.obj) == 0 384 | def __le__(self, other): 385 | return mycmp(self.obj, other.obj) <= 0 386 | def __ge__(self, other): 387 | return mycmp(self.obj, other.obj) >= 0 388 | def __ne__(self, other): 389 | return mycmp(self.obj, other.obj) != 0 390 | return Key 391 | 392 | def sort_points(points): 393 | """ 394 | Given a list of points, sort them lexicographically. 395 | 396 | :param points: The list of points that will be sorted. 397 | """ 398 | return sorted(points, key=cmp_to_key(cmp_points)) 399 | 400 | 401 | __all__ = ['bset_get_vertex_coordinates', 'bset_get_faces', 'set_get_faces', 402 | 'get_vertices_and_faces', 'get_point_coordinates', 'bset_get_points', 403 | 'get_rectangular_hull', 'sort_points'] 404 | -------------------------------------------------------------------------------- /notebooks/islplot-examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "heading", 12 | "level": 1, 13 | "metadata": {}, 14 | "source": [ 15 | "islplot - Plotting of integer sets and relations" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "collapsed": false, 21 | "input": [ 22 | "%pylab inline\n", 23 | "# Load islpy and islplot\n", 24 | "from islpy import *\n", 25 | "from islplot.plotter import *" 26 | ], 27 | "language": "python", 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "output_type": "stream", 32 | "stream": "stdout", 33 | "text": [ 34 | "Populating the interactive namespace from numpy and matplotlib\n" 35 | ] 36 | } 37 | ], 38 | "prompt_number": 1 39 | }, 40 | { 41 | "cell_type": "heading", 42 | "level": 2, 43 | "metadata": {}, 44 | "source": [ 45 | "The points of a set" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "collapsed": false, 51 | "input": [ 52 | "plot_set_points(Set(\"{S[x,y]: 0 <= x <= 8 and -7 <= y <= 5}\"),\n", 53 | " marker=\".\", color=\"gray\", size=3)\n", 54 | "\n", 55 | "plot_set_points(Set(\"{S[x,y]: 0 < x < 8 and 0 < y + x < 5}\"),\n", 56 | " marker=\"o\")\n", 57 | "\n", 58 | "plot_set_points(Set(\"{S[x,y]: 4 < x < 8 and 0 < y < 5}\"),\n", 59 | " marker=\"s\", color=\"red\")\n", 60 | "\n", 61 | "plot_set_points(Set(\"{S[x,y]: 0 < x and y > -6 and y + x < 0}\"),\n", 62 | " marker=\"D\", color=\"blue\")" 63 | ], 64 | "language": "python", 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "metadata": {}, 69 | "output_type": "display_data", 70 | "png": "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\nMni84wAqAAAAAElFTkSuQmCC\n", 71 | "text": [ 72 | "" 73 | ] 74 | } 75 | ], 76 | "prompt_number": 2 77 | }, 78 | { 79 | "cell_type": "heading", 80 | "level": 2, 81 | "metadata": {}, 82 | "source": [ 83 | "Relations between points (maps)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "collapsed": false, 89 | "input": [ 90 | "plot_set_points(Set(\"{S[8,2]}\"))\n", 91 | "plot_set_points(Set(\"{S[2,8]}\"))\n", 92 | "plot_set_points(Set(\"{S[x,y]: 0 < x = y <= 9}\"))\n", 93 | "plot_map(Map(\"{S[2,8] -> [x,y]: 1 < x = y < 9}\"))\n", 94 | "plot_map(Map(\"{S[8,2] -> [x,y]: 1 < x = y < 9}\"), edge_style=\"-\", edge_width=3, color=\"orange\")" 95 | ], 96 | "language": "python", 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "metadata": {}, 101 | "output_type": "display_data", 102 | "png": "iVBORw0KGgoAAAANSUhEUgAAAWgAAAEACAYAAACeQuziAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3X90VPWd//FnAsOPKOCXBVIIhCEoxUag7jYtagnjL4wI\n0V1DWu0e1IYtX2u79nSrgqe7BFnh6Lfaym57ulvxR7fWH6TKIiSpsHSM9VdTA8jvVXAaOkKCUg0y\n/BiS+/3jZjI3kzszd2bunXvvzPtxzhxmMncmb8F55Z33fD5zQQghhBBCCCGEEEIIIYQQQgghhBBC\nCCGEEBm4G9gF7O69LoQQwgEuQQ3nYcAgYAsw1daKhBAiTxQmuX868DZwGugGXgX+zuqihBBCJA/o\n3cAcYDRQBNwATLS6KCGEEDA4yf37gYeAV4CTwHagx+qihBBCQEGKx68G2oGfR74wdepU5eDBg6YW\nJYQQeSJhBicbcQCM6/2zFPhb4NfaOw8ePIiiKI6/rFixwvYapE6pU+rMzxrr6urSSm8jAd0A7AE2\nAt8GutL6TkIIkadqamrweDwpPy7ZDBqgMvVyhBBCRFRWVlJSUkIgEEjpcYNM+N719fX1JjyN9bxe\nr90lGCJ1mkvqNJcb6nRajR6PhzfeeINQKMTJkyfp6elba7Ey0eNSfZNQj6IoiglPI4QQuS0UCtHS\n0kJDQwPr1q2DJBksAS2EEDYoKCgAE1ZxCCGEsIEEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBC\nOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQEtBBCOJQE\ntBBCOJSRcxIuB/4e6AF2AXcAZ6wsyizasxe0t7dTWlpKTU0NlZWVFBUV2V2eEMIkufpaT3ZGFS+w\nDbgYNZSfBxqBpzXHOPKMKrW1tbS2thIMBgmHw31f93g8lJSUUFFRwQsvvGBjhUIIM7j1tW7GGVW6\ngDBQhNptFwFBM4qzUigUorW1lUAg0O8fDCAcDhMIBGhtbSUUCtlUoRAinq6uLqZMmcKaNWs4ceJE\nwmNz/bWeLKCPA48A7cCHwCfAVquLylRLSwvBYOKfI8FgkJaWlixVJIQwauTIkTQ2NrJ7926mTp2a\nMKjz/bU+FdgL/BVqB/0S8I2YYxSnqaurU4Ckl7q6un6P6+rqUi644AJDj5WLXOSSvcukSZNMfa07\nQW9tCSV7k/BLwBvAx723XwQuB57RHlRfX9933efz4fP5kn1fS7W3t6d1XCgU4sYbb6SkpISpU6dS\nVlZGWVkZJSUlDBo0yIpShRA69u3bx6pVq9i6dSvf//73ueuuu3SPS/e1bge/34/f70/pMckCej/w\nz8Bw4DRwDfCH2IO0Ae0EpaWlaR03evRorrvuOt5//31ee+01nn76aQ4ePMhHH31EaWlpX2Brw3vK\nlCmMHDnSiv8MIfJSV1cX8+fPZ+nSpfzHf/wHI0aMiHtsuq91O8Q2rytXrkz6mGSrOADuBW5DXWbX\nBixBfeMwordbd47m5maqq6sHvGmg5fF42LhxI1VVVX1fW7t2LW+99Ra1tbVcd911DB8+HIDTp08T\nCAQ4dOhQv8vBgwc5dOgQRUVFfYEdG+DSfQthnXRf605gZBWHkYBOxnEBHQqFKC8vJxAIxD3G6/Wy\nZ8+efmskjx8/zvPPP8/69etpa2tj/vz5A8I6lqIodHZ2xg1v6b6FsE66r3UnyNuAhtTXRm7ZsoVZ\ns2Yxbtw4ADo6OnjxxRdTCms9et13JLyl+xYic7m8DjpnAxpS21308MMP8+ijj/Jv//ZvLFq0qN99\nZoV1LL3uWxvesd23NsCl+xYiyo07CfM+oFP11ltvcccdd3DJJZfw05/+tK+b1rIqrPXEdt/a8Nbr\nvrUBLt23EM4mAZ3EiRMnuPzyy9m2bRtjx44F1FBcsWIFTz/9tG43rZXNsI4V233Hhrd03yJvKQoU\nmBFt1pKA1tHV1cW+ffv4yle+AsCyZcvYuXMnmzdvprAwurHSSDetZWdY69F237HhLd23yFm7VsJ7\nP4cZ/wIX3Wl3NQlJQPc6cuQIixcvZsuWLezdu5cbbriBQ4cOUVBQQDgc5sorr2TBggUsW7as3+NS\n6aa1nBbWsaT7Fjnp3CloGAU9YRg+Af7W2R8bJAHd68CBA1RXV3PgwAEURcHr9dLY2Eh5eTkAhw8f\npqKigvXr1zNnzpwBj0+1m9ZyeljrSaf7joS4dN/CNuEuWD9KvT54BNR22VtPEhLQvbQBDfCd73yH\niRMn9uuYGxsbWbp0KW1tbX3zaK10u2ktN4Z1LG33rRfeet13JLyl+xaWkoDW5bqA/u1vf8sDDzzA\n66+/3u+4ePNorUy6aa1cCGs9ke5bL7yl+xaWkoDW5bqAPnPmDMXFxbz33nv9uuVE82gtM7pprVwN\n61iR7lsvvA8ePMjHH38s3bdInwS0LtcFNMDNN99MdXU1t912W79jk82jtczqprXyJaz1nDp1Svcz\nT6T7FoZIQOtyZUA/+eSTNDY2sn79+gHHJ5tHa5ndTWvlc1jHStR9J5p9Ry7SfecBCWhdrgzozs5O\npk2bRmdnJ0OGDBnwGCPzaC0rumktCevE0p19l5WVMXHiROm+c4EEtC5XBjTAZZddxgMPPMC11147\n4DFG59FaVnbTWhLWqUl35Yl03y4jAa3LtQG9evVqjh49ytq1a3Ufl8o8WsvqblpLwjpz6a77lu7b\nYSSgdbk2oHft2kV1dXXfrkI9qcyjtbLVTWtJWJsvnV2X0n3bRAJal2sDWm9XoZ5U59Fa2eymtSSs\ns0O6bweRgNbl2oAG/V2FsdKZR2vZ0U1rSVjbI53P+5buOwMS0LpcHdDxdhXGSncerWVXN60lYe0c\n6Xzed6bdd09PDwUFBXFHenpc82H4eRrQnwee09wuQz3Td+SdNVcHdLxdhXrSnUdrabvptWvXUltb\nm9bzmEHC2rms6r7XrFlDU1MTTz31FGVlZUnrcNXppPI0oLUKgSDwZeBw79dcHdAQf1ehnkzm0VpO\n6Ka1JKzdJdm5LocPH97v/JaRy+TJk3nxxRd56KGHWLVqFUuXLo3bTbvuhKw5GNCpmgf8PuZritPt\n379fmTZtWtz7n3jiCaWmpsbQc509e1a54oorlDVr1mRc16lTp5R7771XKS4uVp5//vmMn88sR48e\nVX72s58pV155pTJq1CjllltuUV566SUlFArZXZowoKenRzl69KjyxhtvKL/61a+UBx54QLn99tuV\nOXPmKCUlJcqQIUOU0tJSZejQocqUKVOUcDis+zxNTU2Kx+NRgLgXj8ejNDU1Zfm/MI6znyrKM6iX\n50fYXU1SvX+HpnoC+HauBXRHR4cyatQo5cyZM4aer729XSkuLlZaWlpMqe/NN99Upk+frtTU1Cgd\nHR2mPKdZJKxzyzvvvKN873vfU8aPH69MnjxZ+frXv650d3frHltXV5cwnCOXurq6LP9XxJGDAT04\nhXAeAiwE7ou9o76+vu+6z+fD5/Ol8LT2GzduHBdffDGvvvqq7q7CWJMmTeKJJ57g1ltvzWgeHTF7\n9my2b9/OihUrmDlzpu2zaa3i4mLuvPNO7rzzzr4xyGOPPcbtt98uYxCXOXnyJHfeeSfz5s3jlVde\n4ZJLLkl4fHt7u6HnNXpcvvP7/fj9fsue/0agWefrdv8gSipZB60oivLggw8q3/3ud1N63vvuu0+p\nqqqK24Gkw8ndtJZ01rlPOmhrYaCDTuVdrluAZ1M43lUWLFjAyy+/jJLCG56rVq3ixIkTPPzww6bV\nEemmy8rKmDlzpnPeIY8R6ay3bdvGgQMHmDNnDmvXrmX8+PHceuutbNiwgVOnTtldpshATU0NHo8n\n4TEej4eamposVSTiOQ/4CBihc5/dP4iSMtJB9/T0KKWlpcru3btTem6z59FabummtaSzzh0nT55U\nvF5vwu7Z6/UqJ0+etLtUVR530CeBMcAJg8e7TkFBAQsXLuTll19O6XHaefSxY8dMrckt3bSWdNa5\no6ioiIqKCrxe74BO2uPx4PV6qaiocMYSuxyV9zsJtYzuKtRj1vroeJy2bjpVss7avWQnoTWsWAet\nx+7fFJIyMuJQFEU5ffq0MnLkSKWzszPl72Hm+uh4nLpuOlUyBhGWyOMRR14YOnQoV199NY2NjSk/\n1uPx8Oyzz/KTn/yE1157zYLqYNiwYTz00ENs2LCBFStWsGjRIjo7Oy35XlaSMYgQxkhAx1i4cCGb\nNm1K67FWzqO13DibjkfC2oVOtsP//gxOHk5+rMiIzKBjJDtXoRFWz6O13D6bjkdm1g626WLo2g8X\nzITrd0AKn4xnqRycQUsHHUO7qzBdVqyPjieXumkt6awd7Oxx9c9P3oW/7LC3lhwnAa0jneV2WtmY\nR2vlymw6Hglrhym+Jno9mP7rRCQnAa0jnV2FsbI1j9bK1W5aS8LaAUoWRq9LQFtKAlrHjBkz6Onp\nYe/evRk9z/z58/nGN77B4sWL6enpMam6xHK9m9aSsLbJhCoo6P2cteN/hNCH9taTwySgdaS7q1BP\nNufRWvnQTWtJWGfRkAtgnOa0bx9utq+WHCcBHYdZAZ3tebRWPnXTWhLWWTBhQfR6ML1lqSI5Ceg4\n5s6dy+7du02ZH9sxj9bKt25aS8LaIto59NEtcE7+Dq0gAR3HsGHD0t5VqMeOebRWvnbTWhLWJhp5\nEYz8vHq9+xR0bLO3nhwlAZ1AJrsK9dg1j9bK525aS8LaBLKaw3KykzABM3YVxjp8+DAVFRWsX7+e\nOXPmJH+AhXJ1F2ImZAdjCjpbYOtc9frwErjpsL27CmUnYX4xY1dhLLvn0VrSTQ8knXUKxlwOQ/6P\nev1UUHYVWkACOonIphUz2T2P1pLZdHyphnVTUxN333237f+mWVM4GMZfH70tYw7TSUAnEVluZ/YY\nxwnzaC3pphMzEtYVFRXs2LGDJUuW5E9IyxzaUjKDTkJRFLxeL42NjZSXl5tal5Pm0VoymzYudmY9\nb9489uzZw9/8zd/w1FNPJfw0Q9ecqSSRs5/Ab8aCck69fVMQiibYU0sOzqCNuABoAPYBe4HZMffb\ndkYCo4yeUSWeu+66y7IzpWzevFmZOHFiWmdxsVKunL0lW0KhkPLKK68o3/zmN5XS0lIFUK6//vq4\nxy9atEjxer2Kx+PpdxJWj8ejeL1eZdGiRVmsPkNbr4yeyeS9X9hXR56eUeUxoBG4GJjZG9R5xaxd\nhXqcNI/Wktl0alatWsUPfvADPv30U2655RYeeeQR7r//ft1jQ6EQra2tBAIBwuFwv/vC4TCBQIDW\n1lZCoVA2Ss9cv12FMubIplHAoSTH2P2DKKlMO+hTp06lfa5CI7JxPsNMSDdtrqampgGdc+zF4/Eo\nTU1NdpdqzKf/G+1cnxuuKGGbzi2Zhx30FOAY8CTQBvwCcMlwzDxm7yqMZefndRgh3bS5GhoaBnTO\nscLhMA0NDVmqKEOyq9Aygw3c/9fAd4BW4CfAMuBftAfV19f3Xff5fPh8PjNrdITIrsLbbrvNkuef\nNGkSTz75JLfeeittbW2MHTvWku+TichKjxUrVjBz5kzWrl1LbW2t3WW5Tnt7u6nHOULJQujqfRM+\n+DKU3GBvPQ7k9/vx+/2mPufngA80t78KxO59tvs3haQyHXEoiqIcPXpUGTVqlHLmzBmTqtJ33333\nKVVVVUp3d7el3ydTb775pjJ9+nSlpqZG6ejosLscV6mrq0s43ohc6urq7C7VuI5Xo+OFF0sUpacn\n+zXk4YjjKHAYmNZ7+xpgT7InzUXFxcVMnz7d1F2Fepy2PjoeWTedvpqaGjweT8JjPB4PNTU1WarI\nBLKr0BJGVnF8F3gG2Im6imO1pRU5mJWrOSKcPo/Wktl0eiorKykpKUl4TElJCZWVlVmqyASyq9AS\nRgJ6J1ABzAL+DvjU0ooczKpdhbGc9HkdRkg3nZqioiIqKirwer0DOmmPx4PX66WiosI9m1UiZFeh\n6WQnYQoUC3cV6lm2bBk7d+5k8+bNCXekOYnsQjQuJ3YSatm9qzAHdxK641XvEGaeq9AIt8yjtaSb\nNq6oqIiqqioef/xxXnnlFR5//HGqqqqi4XzmY+g+a2+RqRhwrkJrlqXmEwnoFFnx6XbxuGkerSWz\naRMcfAJ+MwaaZkIoaHc1xsmYw1QS0Cny+XymnavQCLfNo7Wkm85Ah1/9s+sAvH4L9JyztRzDtNu+\n5VyFGZOATpHVuwr1OPXzOoyQbjpNF34LCnpfnsdeg10r7K3HKNlVaCoJ6DSYfa5CI9w4j9aSbjpF\n474KMx6I3t6zGj78rX31pELGHKaRgE7D/Pnz2bJlC2fPZu8NHLfOo7Wkm05R+XL43LXR22/+vTvm\n0f0CehM4fJWXk0lApyFbuwpjuXkerSXdtEEFhXD5r2D4ePX2mY/cMY+WXYWmkYBOUzaX22m5eR6t\nJd20QcPGweXPumseLbsKTSMBnaZs7SrU4/Z5tJZ00wYUz3XfPFrm0KaQgE7TjBkz6OnpYe/evVn/\n3rkwj9aSbtoAt82jJ1RBQe+nGR//I4Q+tLcel5KATlO2dxXGypV5tJZ00wm4bR4tuwpNIQGdgWzu\nKtSTK/NorUTdtKIovPPOOzZXaCO3zaNlzJExCegMZHtXoZ5cmkdrxeumq6ur+cMf/mBzdTZy0zxa\ndhVmTAI6A3bsKoyVa/Nordhuura2lnvvvZfvfe97trw56xhumUfLrsKMSUBnyI5dhbFycR6tNXv2\nbNra2igrK2P16tUcPXqU5557zu6y7OOmeXTsphWREgnoDNmxqzBeHbk2j454//33GTt2LA0NDUya\nNIkjR45QV1dHKBSyuzT7uGUerQ3oD2VXYaokoDNk165CPbk6j77wwgv5+OOPaW5uZs2aNTzyyCPM\nmzeP7u5uu0uzlxvm0dpdhaE/y67CFElAm8DO5XZauTyPHjp0KBdddBHXXnst3/72t9mwYQMjRozQ\nPTYUCtHc3MySJUuYN28eS5Ysobm5OTc7bqfPo2VXYUaMnvIqAHQB3UAY+LLmvrw55VU87777Ljfd\ndBMHDx6MnMbGVo2NjSxdupS2tjbGjh1rdzlZVVtbS2trK8FgkHA43Pd1j8dDSUkJFRUVube++nQn\nNH0RTh1Rb4+dA1dvU8PRCQLPwRu3qNdHfwmqWq35Pnl8yisF8AGX0j+cBequwu7ublt2FerJ5Xl0\nIqFQiNbWVgKBQL9wBgiHwwQCAVpbW3Ovk3b6PDp2V2HkB4lIKpURh/2toUPZvatQT67OoxNpaWkh\nGEz8630wGKSlpSVLFWWRk+fRsbsKg5vtq8VlUumgtwJ/BP7BunLcy+5dhbFyeR4dT0NDw4DOOVY4\nHKahoSFLFWWZk+fRsqswLUaHVFcAR4CxwBZgP9D3qq+vr+870Ofz4fP5TCvQLXw+H1/72tc4duyY\nY+a+2vXR+TCPbm9vN/U414msj47MoyPro50wj56wANq+r16P7CocPNzemrLM7/fj9/tTeozRf7XI\n0OgY8BLqHFo3oPNVZFdhU1MTixcvtrucPtp59ObNmykszN2FO6WlpaYe50qRefS2q0Dpic6jZz1o\nb12RXYVdB6K7CktusLemLIttXleuXJn0MUZerUVAZD3TecA8YFfq5eU+p82hI/JlHl1TU4PH40l4\njMfjoaamJksV2cSp82jZVZgyIwFdjNot7wDeBjYBr1hZlFs5ZVdhrHyZR1dWVlJSUpLwmJKSEior\nK7NUkY2cOI+WXYUpMxLQHwBf7L1cAqyxtCIXc9Kuwli5/nkdAEVFRVRUVOD1egd00h6PB6/XS0VF\nBUVFReqv//sehbe+CR+9bVPFFnLi53XIrsKU5e5A0iZOHXNAfqyPfuGFF9izZw8bN26krq6Oa6+9\nlrq6OjZu3MiePXuim1S6/he2/xMcehK2XA7b74Pu0/YWbzanrY8uHAwT5kdvy2qOpMxY25z3Owm1\nnLarMFY4HObKK69kwYIFLFu2zO5y7HP2E9h8iXrW6YiR02H2UzDmK7aVZYndD8K7P4ze9jXDhOvs\nqcXKXYV5vJNQGOS0XYWx8mUendSQC+Da30Px1dGvde3PzW7aSfNo2VWYEglokzlxV2GsfJhHG3K+\nF67aAhU/h8Hnq19TemDfw9B0ae7Mpp00j5ZdhSmRgLaA03YV6smHebQhBQVw0VKYvyu3u2knzaNl\nV6FhEtAWcMK5Co3Il/XRhuRDN+2U9dHagJZzFSYkAW0B7a5CJ5N5dIx86KadMI8ecaGcq9AgCWiL\nOH0OHSHzaB253E07ZR4tuwoNkYC2iFN3FeqRebSOXO6mnTCPjp1DO3yprl0koC3i5F2FemLn0fX1\n9Zw+7dIAMlOudtN2z6PHXA5DRqvXTwVlV2EcEtAWWrhwIZs2uePXt9h59IYNG9i1Sz4TC8jdbtrO\neXThYJgg5ypMRgLaQpE5tNN3WgIoisLx48dZt24dt956K5///OfZuXOn3WU5S65103bPoycsiF6X\ngNYlAW0hp+8q1Dpz5gy33XYb9fX1zJ49m507d7Jjh/zaOUCuddN2zqNlV2FSEtAWcsOuwohhw4bR\n1tbGsmXL2L9/P4cOHaKxsdHuspwrl7ppu+bRsqswKQloi7lhV2FEYWEhN998Mzt37uTf//3fKSsr\ns7skZ8ulbtquebTsKkxIAtpikV2FH330kd2lGFZYWMi3vvUttm7dancp7pAL3bRd82jZVZiQBLTF\nIrsKZVyQ43Khm7ZjHi27ChOSgM4Ct8yhhQnc3k3bMY+WXYVxSUBngZt2FSYSCoVobm5myZIlzJs3\njyVLltDc3EwoFLK7NGdxezed7Xm0nKswLqMBPQjYDkgbmAa37SrUU1tbS3l5OdXV1axbt44tW7aw\nbt06qqurKS8vp7a21u4Sncet3XS259FyrsK4jAb03cBeQH60pclNuwpjhUIhWltbCQQChMPhfveF\nw2ECgQCtra3SSetxazedzXm0nKswLiMBPRGYDzyOOecwzEtu2lUYq6WlhWAw8a+4wWCQlpaWLFXk\nQm7sprM5j5ZdhbqMBPSPgXsA+ZizDLhpV2GshoaGAZ1zrHA4TENDQ5Yqcik3dtPZmkfLrkJdg5Pc\nvwDoRJ0/++IdVF9f33fd5/Ph88U9NG9pdxWWl5fbXU5K2tvbTT0u70W66ff/E7b/AM59Fu2mgxud\ndWbxyDy66VI49WF0Hn31NnU0YZbIrsKO36m3g5vhwiXmPb8D+P1+/H6/qc+5GjgMfAAcAU4Cv4w5\nRnG6/fv3K9OmTbO7DKW5uVm54oor7C4jZXV1dQrq+w8JL3V1ddEHdYcV5cQHihL+zLa6XeHEB4qy\n9WpFeYbo5deFitJ2r6KcO2V3dVEdr6p1RWrccb/532Pfo9Hn91en/vizn0Yf//wI8+szGQbe00s2\n4rgfmARMAb4ObAMWG8t2EWvu3Lns2rXLVbsKAWpqavB4PAmP8Xg81NTURL/wh3+AjVPgN+Pg97XQ\nvh7OnbS4Uhdyy2x6XKX182jtHFp2FQKpr4N23ztcDuLWXYWVlZWUlJQkPKakpITKysroFz56S/2z\nO6SG8+9rJazjccts2up59MiLZFdhjFQC+lWg2qpC8oUbdxUWFRVRUVGB1+sd0El7PB68Xi8VFRUU\nFRVF7/jyL2DUF/o/kYR1Yk7vprOxPlp2FfZjxrK53nGKcx04cIDq6moOHDhgdyl0dnYybdo0Ojs7\nGTJkiN3lpCQUCtHS0kJDQwPt7e2UlpZSU1NDZWVl/3DW+mSPGsKH18OncVawDCqCkhugdJG6Hnbw\nedb9R7jFZwF4ewl0/E/0awWFMP0HMHMlDBpmW2l0vArbrlJ/eACU3w+zHjTnuTtbYOtc9XrRRLix\nXf0Nw4hwF6wfpV4fPAJqu8ypySIF6n9Xwv84CWgbXHbZZaxatYprrrnG7lKyS8I6NYrSf6VHxMjp\n9q/02P0gvPvD6G1fM0y4LvPn7TkHL46Ds39Rb1e1wehLjT02BwNaPovDBm76jGhTXVAOM+vhhj0w\nfzdcskLGIIk4eTZt1Ty6cDCMl3MVRkhA28DNuwpNI2FtnBNn01bOo+VD/PtIQNvAzbsKLSFhnZwT\nu2mrPq8jdldh6MPMn9OlJKBt4KZzFWadhHViTuumrfi8jthzFX7ormWpZpKAtombP90uaySs9Tmt\nm7ZiHi1jDkBWcdjm9OnTFBcXc/DgQV5//XWuuOIKxowZY3dZ7iCrQaKcstLjdCc0fTH6IUdj52T2\neR1d78Gmaer1QcPh5o9h8PDEj5FVHMIM7733Hp2dnX27CpcvX5704zyFhnTWUU7pps2eR8uuQkAC\n2hZ79+5lzpw5XHbZZWzYsIEPPviA6dOn212WO0lYq5wwmzZ7Hi1jDhlx2OWxxx7jscceo6Ojg8mT\nJ8uKDrPl8xjEzl2ISg/8rkr9sCOAoWPg+h1QlPizXHRpdxUOL4GbDifeVSgjDmGWu+++mzvuuIPT\np08zbtw4u8vJPfncWdvZTZu5Plp7rsJTwbw8V6EEtI1++MMfsnDhQmbNmmV3KbktH8Paztm0WfNo\n2VUoIw6Rx/JlDGLXSg8zPq8j8By8cYt6ffSXoKo1/rEy4hAih+RLZ21XN23G+ug831UoAS0E5EdY\nZ3s2bcY8Os93FUpACxErl8M62920GfPoPF5uJwEtRCK5GtbZ7KYzXR+dx+cqlDcJHSCtM5UIe+XS\nG4zZWDed6froTdOhq/f1O3eT+vcaKwffJDQS0MNQz0c4FBgC/DewXHO/BHQGamtraW1tJRgMEg6H\n+77u8XgoKSmhoqKCF154wcYKRVK5ENbZWOmRyed1bL8H9v1IvX7hUvjyzwcek4MBbWTEcRq4Evgi\nMLP3+lczLU6onXNrayuBQKBfOAOEw2ECgQCtra2EQiGbKhSG5MIYJBuz6Uzm0bEnk3V4U2gWozPo\nSEIMAQYBx60pJ7+0tLQk/ZCkYDBIS0tLlioSGXN7WFs9m053Hp2nuwqNBnQhsAPoAH4HyAdHmKCh\noWFA5xzU44j0AAALIUlEQVQrHA7T0NDQ/4s95+Dkn6D7rIXViYy5Nayt7qbTWR+dp7sKjX5Yaw/q\niGMU8FvAB/gjd9bX1/cd6PP58Pl8JpWX29rb21M/7vQxaLxEnecVFELRJDh/KpxfNvAyZLTxU9YL\na11QHg3seDPrSFi3r3fGzDrSTWtn05FuOrgx/dl0ZH10ZB4dWR+dbB5dshD+9Gv1evBlmPEv6fxX\n2cbv9+P3+1N6TDqv3n8GTgG9E3t5kzBdS5YsYd26dUmPq6ur4/HHH1dvfPhb8FcZ+waekb1hrRPg\nRaUwaEgG1QtTuOUNRitWenS8CtuuUkMfoPx+mPVg/OPPfgK/GQtK70aXm4JQNCF6fw6+SWgkoMcA\n54BPgOGoHfRKIPIvJQGdpubmZqqrqxOOOTweDxs3bqSqqjeUu0/D9nvhzy/1/lqY5t+9dN/O4/Sw\ntmKlR6qf1/E/V0HH79TrX/4FXLgkel+eBvQM4GnUOXQh8F/A/9PcLwGdplAoRHl5OYFAIO4xXq+X\nPXv26K+H7j6tzqI/O6S5HIxez2SOKd23vZwc1mZ206muj973KGz/J/V6STXM/e/ofXka0MlIQGfA\nsnXQigJnjsUPb+m+3cOJYW1mN53K+uhE5yqUgNYlAZ0hW3YSSvftTk4La7O66VTm0fF2FUpA65KA\nzjXSfbuDU8LarG7a6Dw63q5CCWhdEtD5Jrb7PnEQTh6S7ttOTgjrTLtpo/PoeOcqlIDWJQEtomK7\n79jwlu7benaGdabdtJF5dM85eHEcnP2LeruqDUZfKgEdhwS0MM7y7jtOeOdr921XWGfSTRuZR7/+\njeimlRkr1U0rEtC6JKCFOSzvvkv1wztfuu9sh3Um3XSyebTeuQoloHVJQIvskO7bPNkM63S66WTz\naL1dhZ7zJaB1ODagP/vsM1avXs3ixYu58cYb8fv9PPXUUyxfvjz5g4W7yOw7fdkI63S66WTz6H67\nCv8TJn9NAlqHYwO6u7ubL3zhCyxfvpw1a9Zw3XXXMWTIEH70ox8lf7DILdJ9G2N1WKfaTSeaR8fu\nKrz8vySgdTg2oAGeeeYZfvzjH3P8+HE++eQT9u3bR3Fxsd1lCSeR7lufVWGdajcdbx4du6uw+gN4\n6XPqbQnoPo4O6O7ubi688EKOHj3KXXfdJd2zSJ22+z7Ru2En39Z9Gw3rCfNhcq2xsDbaTSeaR2t3\nFV7xArxeq16XgO7j6IAGeOSRR7jnnns4cuSIdM/CXNruWy+8Tem+4wS4Xd23obAeDhNuSB7WRrvp\nePPoncujuwrL7oBDT6rXJaD7OD6gu7u7ee211+REAiL7stJ96wR4trpvs8LaSDetN48ef110V+Gw\n8XC6N8AloPs4PqCFcKSsdd86AW5F951pWBvppmPn0ZWb4O071L/HQefByGnwl+0wYQH4nH1aLAlo\nIdwsa913TIib0X1nEtaJuukZK6Dlpug8+q9mw6x/hf2PwuRb1NNiHfs9FF8V/RhSh5KAFiJXZbX7\njunAU+2+0wnrQUXxu+lLH4XW/wuhdld0yvFIQAuRr7LdfZ9XBiOmJu++Uw3rUeXwx38c2E3PbQQl\nDOMq1XpcSAJaCDFQtrvvSHjHdt9Gw3r8fBg6Gv70bLSbLv8hzFqVXo0OYVZATwJ+CYxD/Vf7T2Ct\n5n7HBrQtZyoRwu3s6L7pgWNvwJ9fjB/WhUPhvClq2Ff8FM6b3HeXG1/rZgX053ovO4DzgXeAm4B9\nvfc7MqAtO9efEPksG933sGLoCavPdaZz4HFfXQ+lNX033fpaNxLQOmdlHOBo7wXgM9RgnkA0oB0n\nFArR2tqqe7bscDjc9/VQKOTYn65COFJBAQwbp17GzB54fybdt9KjPvbkn+IfUzgURn2h72auv9aN\nBLSWF7gUeNv8UszT0tJCMBhMeEwwGKSlpYWqqqroF4+9AYFfQ89ZiysUIo8MHadeRn9FDfBzXRA+\noX5+87neP8MnoNvA6KRwKIy8uO9m2q91l0gloM8HGoC7UTtpx2poaOj3q46ecDhMQ0ND9B/t3Enw\nX6/+zyKEcKbB5/Vb4pfWa91FjAa0B/gN8CtgQ+yd9fX1fdd9Pp/tW6rb29tTP65wqPoGhgS0EM41\n+q/73UzrtW4Tv9+P3+9P6TFGAroAWAfsBX6id4A2oJ2gtLQ09eMKB0PVdvhwk4w4hHAizwXqTkGN\ntF7rNoltXleuXJn0MUYC+grg74F3ge29X1sONKdcYZbU1NTwy1/+MuGvPh6Ph5qamv5fHDYGym63\ntjghhGnSfq27RE5uVAmFQpSXl+u+sxvh9XrZs2ePK9/ZFUKo3PxaN2uZnesUFRVRUVEBkHBtpNP+\nwYQQqcn113pOdtARbtxdJIRInRtf6/JZHEII4VBGArowO6UIIYRIlQS0EEI4lAS0EEI4lAS0EEI4\nlAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0EEI4lAS0\nEEI4lAS0EEI4lAS0EEI4lJGAfgLoAHbFO2DJkiU0NzcTCoVMK0wIIfKdkYB+EqhKdMC6deuorq6m\nvLyc2tpacyozmd/vt7sEQ6ROc0md5nJDnW6o0SgjAf0a8JdkB4XDYQKBAK2trY7spN3yjyZ1mkvq\nNJcb6nRDjUaZPoMOBoO0tLSY/bRCCJF3TA/ocDhMQ0OD2U8rhBB5x+hZvb3Ay8AMnfvklN5CCJG6\ng8CFiQ4YbMI3MRryQgghTPYs8CFwBjgM3GFvOUIIIYQQQrhU0g0sDjEJ+B2wB9gN/KO95cQ1DHgb\n2AHsBdbYW05Cg4DtqO9LOFkAeBe11j/YW0pcFwANwD7Uf/fZ9paj6/Oof4eRy6c493W0HPW1vgv4\nNTDU3nLiuhu1xt291003B7gU5wf054Av9l4/HzgAXGxfOQkV9f45GHgL+KqNtSTyfeAZYKPdhSTx\nATDa7iKSeBr4Zu/1wcAoG2sxohA4gtr4OI0XOEQ0lJ8HbrOtmvguQc3NYajNzhZgqt6BmSyzM7SB\nxQGOonalAJ+hdioT7CsnocgOnyGo/3DHbawlnonAfOBx3PEGsZNrHIXa6DzRe/scanfqZNegrj44\nbHchOrqAMGqjM7j3z6CtFembjvrb8mmgG3gV+Du9A/Ptw5K8qF3/2zbXEU8h6g+TDtSxzF57y9H1\nY+AeoMfuQgxQgK3AH4F/sLkWPVOAY6gfp9AG/ILob1FO9XXU0YETHQceAdpRFzZ8gvrv7zS7UX8w\nj0b9974BtfExnRfnjzgizkd9od5kdyEGjEIdcfhsriPWAuCnvdd9OH8GPb73z7GoP/jm2FiLni+h\ndnwVvbd/AjxgXzlJDUH9gTLW7kLimIra1PwVagf9EvANWyuK75uoefQq8DPUxmeAfOmgPcBvgF8B\nG2yuxYhPgc2oL2AnuRyoRp3tPgtcBfzS1ooSO9L75zHUF+uXbaxFz597L629txuAv7avnKSuB95B\n/ft0oi8BbwAfo46LXkT9f9aJnkCtdy5qp3/Aim/ixfkddAFqiOj+hHKQMajv6AMMB1qAq+0rJ6m5\nOLuDLgJG9F4/D3gdmGdfOXG1ANN6r9cDD9lXSlLP4cw33SJmoY4PhqO+7p8G7rK1ovjG9f5Zivq+\n2Eizv4FbNrB8FXVeuoPoMqGEH59qkxmoc8gdqEvD7rG3nKTm4uxVHFNQ/y53oL5ol9tbTlyzUDvo\nnagdn1NXcZwHfET0h55T3Ut0md3TqL89O1ELap07gCttrkUIIYQQQgghhBBCCCGEEEIIIYQQQggh\nhBBCCCGEEELkuv8PkLq5nBQ5tbgAAAAASUVORK5CYII=\n", 103 | "text": [ 104 | "" 105 | ] 106 | } 107 | ], 108 | "prompt_number": 3 109 | }, 110 | { 111 | "cell_type": "heading", 112 | "level": 2, 113 | "metadata": {}, 114 | "source": [ 115 | "The shape of a (basic) set" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "collapsed": false, 121 | "input": [ 122 | "plot_set_points(Set(\"{S[x,y]: 0 < x, y < 10}\"),\n", 123 | " marker=\".\", size=5, color=\"gray\")\n", 124 | "bs0 = BasicSet(\"{S[x,y]: 1 < x and 3y + x < 20 and x - y < 2}\")\n", 125 | "plot_bset_shape(bs0, color=\"orange\")\n", 126 | "bs0 = BasicSet(\"{S[x,y]: 5 < x < 9 and 3 <= y <= 8 }\")\n", 127 | "plot_bset_shape(bs0, color=\"lightblue\")\n" 128 | ], 129 | "language": "python", 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "metadata": {}, 134 | "output_type": "display_data", 135 | "png": "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\n", 136 | "text": [ 137 | "" 138 | ] 139 | } 140 | ], 141 | "prompt_number": 4 142 | }, 143 | { 144 | "cell_type": "code", 145 | "collapsed": false, 146 | "input": [ 147 | "plot_set_points(Set(\"{S[x,y]: 0 < x, y < 10}\"),\n", 148 | " marker=\".\", size=5, color=\"gray\")\n", 149 | "plot_set_shapes(Set(\"{S[x,y]: 1 <= x,y <= 3 or 5 <= x,y <= 7}\"),\n", 150 | " color=\"orange\")" 151 | ], 152 | "language": "python", 153 | "metadata": {}, 154 | "outputs": [ 155 | { 156 | "metadata": {}, 157 | "output_type": "display_data", 158 | "png": "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\n/9y5uV5ywCHSAAAAAElFTkSuQmCC\n", 159 | "text": [ 160 | "" 161 | ] 162 | } 163 | ], 164 | "prompt_number": 5 165 | }, 166 | { 167 | "cell_type": "heading", 168 | "level": 2, 169 | "metadata": {}, 170 | "source": [ 171 | "The groups defined by a map" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "collapsed": false, 177 | "input": [ 178 | "plot_set_points(Set(\"{S[x,y]: 0 <= x, y <= 10}\"),\n", 179 | " marker=\".\", size=5)\n", 180 | "plot_map_as_groups(Map(\"{[x,y] -> [floor(x/4), floor(y/6)]: 0 < x,y < 10}\"),\n", 181 | " color=\"orange\")" 182 | ], 183 | "language": "python", 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "metadata": {}, 188 | "output_type": "display_data", 189 | "png": "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\n", 190 | "text": [ 191 | "" 192 | ] 193 | } 194 | ], 195 | "prompt_number": 6 196 | }, 197 | { 198 | "cell_type": "code", 199 | "collapsed": false, 200 | "input": [ 201 | "plot_set_points(Set(\"{S[x,y]: 0 <= x, y <= 20}\"),\n", 202 | " marker=\".\", size=5)\n", 203 | "plot_map_as_groups(Map(\"{[x,y] -> [X,Y]: X = floor(x/4) and Y = floor(y/6) and 0 < x,y < 20 and (X + Y) % 2 = 0}\"),\n", 204 | " color=\"orange\", vertex_size=6)\n", 205 | "plot_map_as_groups(Map(\"{[x,y] -> [X,Y]: X = floor(x/4) and Y = floor(y/6) and 0 < x,y < 20 and (X + Y) % 2 = 1}\"),\n", 206 | " color=\"blue\", vertex_size=6, vertex_marker=\"v\")" 207 | ], 208 | "language": "python", 209 | "metadata": {}, 210 | "outputs": [ 211 | { 212 | "metadata": {}, 213 | "output_type": "display_data", 214 | "png": "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\nAAAAAElFTkSuQmCC\n", 215 | "text": [ 216 | "" 217 | ] 218 | } 219 | ], 220 | "prompt_number": 7 221 | }, 222 | { 223 | "cell_type": "heading", 224 | "level": 2, 225 | "metadata": {}, 226 | "source": [ 227 | "Illustrate a polyhedral domain (and related information)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "collapsed": false, 233 | "input": [ 234 | "domain = Set(\"{[i,j]: 0 <= i < 6 and 0 <= j < 6}\")\n", 235 | "dependences = Map(\"{[i,j]-> [i+1,j+1]; [i,j]-> [i+1,j]}\")\n", 236 | "tiling = Map(\"{[i,j] -> [floor(i/2), floor(j/2)]}\")\n", 237 | "space = Map(\"{[i,j] -> [i,i+j]}\")\n", 238 | "plot_domain(domain, dependences, tiling, space)" 239 | ], 240 | "language": "python", 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "metadata": {}, 245 | "output_type": "display_data", 246 | "png": "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\na7ZTqKmpITU1leLi4haXdbZFvZ5bWVkZKpXqDkdYnx+zfPlqfvppJx4ez2M0fkptwqTjhTBlpCE7\nGRmZ2ygtLWXDhu9YvlzN5cvXqaqaj9WaCTzk6qFJYsuWLXh4eBAfH39L9crKykoyMzPRaDQIIQgP\nD+f111+3K5cEap8WNm3aRNeuXR0m2dIUGzdupFOnTkyZMuWWsOuioiLWrFnHypVrqKz0q0uYXAl0\ndOp4ZKQhOxkZGWrv7g8ePMinn6pJTd2Gh8ezGAxLgadoa3fBEyZMwNfXF4VCgRCCc+fOodVq+f33\n3xk8eDATJkygV69eLZJZycvLY/v27YwZM4YRI0Y4tFrl3Zg+fXpD0EJ9VNny5WoOHvwVhWI6VVXf\nASNxh1BxmX8iy8pIwJ1lSlrCvWpXc2RKiouLWbv2a1asUFNW5kFlZTxCvAI84JrBN4l0WZnq6mqO\nHDnCyZMnadeuHREREYSEhNgtfFmP1Wpl//79ZGRktFiypZ7mzFd+fj5ffLGGr75ai8XSuy7oIhb3\nTJiUZWVs/mcLEUePHiUrKwudTodSqWTp0qVO7M45LFmyBLPZTFBQECEhIQ2Ci7Jd7olSqbQpuFhd\nXc2ePXtYtmw1+/fvwcPjRYxGFfAY7nsXrMSW4KLZbG74+9KlS2RkZBAWFka3bt0c8qRhMBhISUnB\nYrEwdepUu5fZbkbKfFVWVrJ161aWLVOTkXESq3U2NTXzAelJl61P8+arrXD7daNOkdl1sjK+vr5E\nRkYSGRlJQkICBoOhTd0hGwwGzGYzcXFxt7wu2+We1Kv+NoXFYqFTpx5YrT3r7oLXANIjqVxDHk1d\nsKDWrry8vIY75B49ekhOuJRCvWTLww8/TExMjEOixqTO1/33d0OpjKjba5mM+ydMNn++2gJ3u27Y\notUWm4OCghxeq9vZpKen29Rbku1yHxYtWiSpXVlZb/T6Y8BruL+DAZBml1T7m4tWq+Xbb7/l6aef\n5plnnnFYWLLU8VZVDaWiYg8wA/d3MODq+XIWUq4bjdFqTiY0NJQDBw60VncO4cCBAzYTyWS73Ie0\ntDRJ7RSKk04eiWNRKKTZJdV+qZjNZlJTUzl06BBz585l6FDH6nlJn6/m1ZR3Na6aL2cj5brRGK0W\nXebr64ter2+t7hyCXq9vsl48yHa5EyaTSVI7IaS1cxekjleq/VIoKysjMTGR9u3bo1Kp8PFx/BOE\n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\ntwQEqAkIMPD66/OZP3+u5OWgmyVbJk2ahI+PDyaTiZqamoaiXI7ixo0bbNq0iS5dujBhwoRbKmXe\nTkVFBRkZGWi1Wjw8PAgPD2fYsGGS92yuXr1KYmIiPXr0YPz48Xh5eVFdXY3VapUkR+K8jf+LeHqu\nxcdnDT16dOKtt+J5+eVZkj+H9QrUQ4YMaVhGMxgMKJXKJt/PPwPyxr9Mq9NY/fgxY8bQv3//Zqn3\n1u8dDBs2jDFjxjQcq1QqXSR3YwV+xc9PjcWyg2efHc/bb3/SrH0NgHPnzrFlyxYee+wxHnvssYY7\naC8vL4fLjJw5c4Zt27YRHR1NZGRko3frVquVs2fPotVqyc/PZ8iQIUyaNImHHnqoWcuROTk5pKam\nMnbsWCIiIhqOdZYkjW1MwA4CAlZjsRxm5syZLFqUQkRERLPOcvLkSXbv3s24ceMIDv5n/RpXh/i3\nJWQnI+MQDAZDw12w1Wq1u358/Xr+L7/8wgsvvCAxI9yZFDUkTHbu7FuXMPkpnTp1atZZ6tfzf/vt\nN6ZMmWJzr6YlCCH49ddf0Wg0xMbG3hIdVU9paSknT55Eq9USEBBAREREw1NVc7Barezbt4/s7Gxe\neuklh9awsY8zKJVr8PJaz6BBA1i8WMX06UnNdgpms5mffvqJc+fO8eqrrza5VyPTNLKTkbEbIQT5\n+floNJqG+vHPP/+83fXj6/cOLl++zLx585p9IXccZuBH/P3VWK2/MmXKNN588ztGjhxpl13V1dVs\n3boVvV7fIskWqdTviSxYsOAWJ19fKEuj0XD58mVCQkKYNWsWXbt2taufyspKUlJSEEKgUqkcvtQn\nHSOQQmDgaiCHuXPnsHDhL3bfoJSXl5OYmEhgYCAqlarFJav/7MhORqbZlJeXN9wF19ePnzBhQou+\njDdu3CAxMZHOnTsTHx/vorXufDw9E/DxWUufPg+xeLGKGTO+adHy3M2SLVOnTm2RZItUoqKi6Nix\nY0NQxdWrVxsSJh944AHCw8OZMWNGi5bnLl26RFJSEsHBwYwdO9ZhhcyaRwbe3moUiu8YPnwkixcv\nYuLEiS367OTn55OSksIjjzzidIFQmZYj3n33XbF7925RWVkp2jKVlZVi9+7d4t133xWLFy++Z+zK\nzc0VMTExwtvbWygUCuHt7S1iYmJEbm7uHW2tVqvIyckR3333nfj73/8ufvjhB3Hp0iVhtVpbPI4z\nZ86Ijz76SBw+fNgh56u3y8PDQwBCofAWECMgV4C47adKwPciICBG+Pt3Eq+99pbIzMxs8RiEECI7\nO1t8+OGHQqvVOuR8zZkvs9ksTp48KRISEsQ//vEPsXv3bnH16lWHjOP48ePiww8/FKdOnXLI+ert\nUig8BChszFeZgC9FYOAI0alTT/Hv//6eKCgoaPEYrFarOHTokPjoo4/E2bNnHWBV8+arLXHz9ZDa\nEMsmaYmbvh9QA0F1Hc0DjtzsZI4ePUpWVhY6nQ6lUsnSpUtb0J1rWLJkCWazmaCgIEJCQhqEJNu6\nXUql0qaAn9lsbvi7tLSU1NRUgoODGTp0qEOeNIQQpKWlceLECaZOneqQ8F1bdtUKLpoBHV5ea/D0\n/IaQkFAWL1bx4ouTHbI0YrVa2b17N7m5uS2SbLmZ5s7X+fPnOXz4MGFhYY0mTNqD2Wxm586dXLx4\nkdjYWJu5TVKQNl8m4HBdqHgKo0c/xdtvx/PMM884xK7q6mq2b99OaWkpsbGxDlnObO58tRVuvx7W\nKTI7LbrsE2AnMK3uPHcsyPr6+hIZGUlkZCQJCQkYDIY2FZVhMBgwm83ExcXd8npbtysvL8/GF/uf\n6/f1Qn7333+/QxPqqqqq2LJlC0ajEZVK5ZCIMSl2gQVf3zB8fIqJj4/jX/7liEMz7OslW7y8vFiw\nYIFdlQRvx5756t27t0NzbkpLS0lMTKRjx44OW86UOl/t2g2gQwdP3ngjnnnz8hyac3P16lU2bdpE\nr169iIuLc8hypj3z1Ra42/XQFva+o+2BaODVur/NQFlTBwQFBZGenk5MTIydXbY+6enpt+hMNUZb\ntGvRokWS2+3evdvh/V+5coVNmzYxcOBAYmNjHXI3CtLtGjhQcOLEBYfvjxQWFrJ58+ZmS7bYwtXz\ndfbsWbZs2cLjjz/Oo48+6rB9Cql2BQV14NixYw7fHzl16hQ7duzgqaeeanZoc1O4er6chZTrYWPY\nO2thwJfAKWAYcAJ4CzDc1EbcXEXNaDSSmprK+++/b2eXrc97773HhAkTmrwbbYt2+fj4UFNTY7Od\nt7c31dXVDu07MzOTn376ieeee87h2mOusksIwbFjx0hLS2uWZItUXGnXwYMHOXr0KFOnTqVPnz4O\nOze4zq6bVSRiY2MdLnDpyu+XM2nselj3HXbKcpkSiAAWAceA5cC/Ae/e3GjVqlUNv48cORK9Xm9n\nd65Br9fbXO7w9fVtc3aZTCaHtpOCxWLh559/5vfff2fOnDlOkRlxhV0mk4nU1FSuXLkiSbLF3j4c\n2U4KVVVVbN26lcrKSlQqVZNaZfbiCrsqKytJTk5GoVCwYMECpyxzu8Ku1kCv15Odnc2xY8eadZy9\nTuZi3U99b5updTK3sHDhwobfjUYjBQUFdnbnGgIDAzEajTafZFyTgd58rl+/zvr130hu76gMdL1e\nT1JSEr6+vk7NO/Dy8pJ0B+kou65fv05iYiIPPvgg8+fPd3jGfmFhIatXr5Xc3lH9FxcXs2nTJvr3\n78/06dMdtpx5O609X3dTkXAUp06d4rPP1JLbO/rz4mwCAwMJDg6+pSbQ559/bvM4e9/lP4BCoH5d\nIAbQNXVAZmYm0dHRdnbnGqKjo7l5ya8x3N2u+ozsSZNeonv3fvznfx5DCGkVEEeNGtXi/s+fP8/q\n1asZMGAAM2fOdGpim9TxOsKu06dPk5CQQEREBJMnT3bYBcNkMpGSkkJ09HgGDQrjww+LEeLuhb5u\nxhF2ZWVlsX79ekaNGsX48eOd5mCg9eZL1ClQb9y4kXHjxjk0r6eyspKEhLWEhDzOiBExfPWVL0I8\nJulYR8xXayLletgYLdn5fAPYAHgDZ4EmQw50Oh2zZs1qQXetT1RUFLt27aoP02sUd7Xr8uXLrFmz\njs8+W0NlZQAVFSrgM6ADkAfYzoZeuXKl3f0LIThy5AiHDh1i8uTJDBgwwK7z/P777+zevRt/f3/8\n/f1RKpV4enoyYcKERscrJcu7pXbVS7bMmDHDYQXGTp8+zeefryEhYT1CDEavj6d2gcCP1pgvi8XC\n7t27OX36NK+88ordKgDHjh3jxIkT+Pv7ExAQgEKhoH379jz55JONjtfZ82Uymdi5cyeXLl1ymIqE\nqJM+WrFCTVJSIp6eT1BRsQQYT+0l1fnz5QqkXA8boyVOJgNo8hbLaDSSmZmJTqfDy8urTYX5Qq0I\nnlKpJCEhgaCgIEJDQxvyZNzRrnr5+OXL1aSnHwCmU1X1PTCCW/fmBlObf9B0HL+94ZW3y723pKhT\nv379mDhxInl5eeTl5VFSUnLX89WXDrCVn2CvXbfLvTdXl62x8yUlbWb5cjW5uXmYza9iMv3KnXXt\nnTtfer2ezZs34+Pjg0qlalHYdVhYGF26dCEnJ4e8vDxKS0vvqtPm7PlytIrEjRs3+PbbDSxfrubK\nlXKMxnis1izgdr02586Xq2jseuhq7pnMeHfP+D979qz43//7P8T993cXgYGPCVgjQN9ItvTtP7kC\nYoRC4SkAh2QkX716VXz22Wdi69atoqampkV2FRcXix9//FF8+OGHYt26deL7778XX3zxhaiqqmry\nOGdkWhcVFYnly5eLH3/8UZjNZrvPI4QQWq1WzJ//uvDz6ygCAsYJSBZQ3Yz58nCYXefPnxf/8z//\nI/bv398itQWr1SoKCwvFtm3bxN///nexceNGkZCQIDZu3CgsFkuTxzpjvhylImG1WsX+/fvFiy/O\nFu3atRd+fjMF7BFgacZ8KeSMf2c5GSHXk3Ea1dXVbNmyhWXL1GRmZmC1vkJNzXxqBRiah4/PIv7x\njyGS4/vvRm5uLj/88MMdcu/N4fb68cOGDSM8PJz8/HwOHz7M5MmTKS0tbSh+NmbMmBaNWQoZGRn8\n/PPPd8i9N4fy8nI2bNjIJ5+oKSwsprp6HhZLHHCnQrIt2rePYfPmf2tRbpYQgqNHj3LgwAEmTZrE\nwIED7TqPwWBoqJxpNpsbatAcOnSIoqIinnnmGa5cuUJmZibDhw93eslsIQQHDhzg+PHjLVKRuHLl\nCgkJ61mxQo1e701lZTxCvAI0f7nN378nOTnpDltadSfkejL3IDqdjs8+U/PNN9+iUISh16uAyYCr\n6nbUBhf88ssvZGVl2S33XlRUxIkTJ9DpdPTs2fOO+vEbN26koqKC5ORkPD09uX79ulPl8qF2n+LH\nH38kPz/fLrl3IQTp6el8+qma7du34OkZQ2Xl36iNk3HehrotampqSE1NpaSkhPnz59OhQ4dmHS+E\noKCgAI1Gw5kzZxg0aBDjxo2jd+/eKBQKqqurOXXqFEajkS1btlBTU4Ner2f06NFOsqiWlqpIWCwW\nfvrpJ5YvV5OW9gsKxVSqqr4GHsG59+P3NrKTaQNUVFSwaVMiy5at5ty5C5hMcZjNvwH9XD00DAYD\nycnJdsm9V1VVkZWVhUajsVk/fs6cOVRVVbFz507MZjMdOnTgkUcecaQpt1BeXk5SUhIBAQHEx8c3\nKyqupKSEdeu+YcUKNTduWOvugpcCrq9Jcv36dTZt2kS3bt2YN29es6Li9Hp9g/q2l5cXERERjB8/\n/o49HB8fH+Li4jAYDGzdupUOHTrw4IMPOvWmoCUqEgUFBXz11Vq+/DIBk6l7XdDFeqBtpCa4O7KT\ncVNEXRb5ihVqkpOT8PQcTUXFfwDP4S7TZo/cu6irH6/VasnNzWXAgAHExMTQr1+/JpfXysrK2Lx5\nM+Hh4RQWFjJ8+HCnycsXFBSQnJzcLLn3+izy5ctXs3fvz3h4TMJo/BJ4Ane5C87Ly2P79u2MGTOG\nESNGSLbrzJkzaLVazp8/z9ChQ5k6dSrdu3dv8viioiJ27NjB2LFjOXjwIFOmTHGkKbdgj4pETU0N\n27ZtY9kyNVrtCazWl6ip2QG4fjP7XsM9rlYyDVy/fp1vvtnA8uWrKSmprItg0QGOlb5oKRqNhr17\n9zJhwgQefvhhm+0rKyvJyMhAo9GgUCiIiIjg6aeftvnkU+9s09LSmDRpEu3bt0er1TolskUIweHD\nh0lPT2fKlCn062f7SfHixYusXr2WVavWUFXVoS5U/CtqRcrdA6vVyv79+8nIyGDWrFk89NBDNo+5\nceMGWq2WkydP0r59eyIiIpgyZYrNCK16Z3vq1Clmz57NjRs3aN++vVP2I+xRkcjJyWHVqjV1SclB\ndU8tW4GWC5nKNI7sZNwAIQT79+/nk0/U/PjjDjw9x2MwfAKMxv58WedQL/deWFhIXFxck3LvVquV\nc+fOodFoGurHT5w4kZ49e0q6i25MsqW6upqXX37Z4UmCN8u9q1SqJuXe6yt4fvzxao4ePQzMpLo6\nhVqlJffCYDCQkpKCxWJhwYIFTTp1s9lMbm4uGo2GK1euEBISwuzZsyXvRVVWVrJ582Y8PT1RqVT4\n+fkREBDgcG0waJ6KRGVlJYmJSSxfrubMmbOYTHMxmw8B9uVuyTQP2cm4kKKiIhIS1rNy5RoqKtrV\n3QV/ij0RLK1BaWkpSUlJ3H///ahUqrve1ZaVlTXcBfv7+xMeHt7s+vF3k2zx8fFxeL11qXLvZ86c\naUiYtFoH1N0FJ9JIlQu3oKioiMTERIYOHcpTTz111+XF4uJiNBoNWVlZdO3alYiICIYMGdIsleq7\nSbY4Q3Lp/PnzJCcnM2LECKKjo+96w3LixAlWrlSzadMmPD2jqKj4V2oTJtuWnEtbR3YyrYzZbOan\nn35i2bLVHDz4KwrFNKqqvgUicZe1+8awJfdeXx9Dq9Vy6dIlgoODmTlzpl2Z46dPn2bbtm3N2juw\nl5ycHFJTU+8q9240GklOTmH5cjU6nQ6LZQ4m0y9Iyeh2JVqtlj179vD8888zdOjQO/6/pqYGnU6H\n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\nSUVORK5CYII=\n", 247 | "text": [ 248 | "" 249 | ] 250 | } 251 | ], 252 | "prompt_number": 8 253 | } 254 | ], 255 | "metadata": {} 256 | } 257 | ] 258 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # islplot installer 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name='islplot', 7 | description='Python module to render isl integer sets and maps', 8 | version='0.1', 9 | author='Tobias Grosser', 10 | long_description=open('README.rst').read(), 11 | packages=['islplot'] 12 | ) 13 | -------------------------------------------------------------------------------- /tests/README.txt: -------------------------------------------------------------------------------- 1 | To run the unit tests, ensure that an installed version of islplotlib is in 2 | your PYTHONPATH variable. Then run the following command in this directory: 3 | 4 | python3 -m unittest *.py 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/plotter.py: -------------------------------------------------------------------------------- 1 | from islplot.plotter import * 2 | from islpy import * 3 | 4 | import unittest 5 | from test import support 6 | 7 | class TestPlotter(unittest.TestCase): 8 | def setUp(self): 9 | return 10 | 11 | def tearDown(self): 12 | return 13 | 14 | def test_do_not_fail_on_empty_bset(self): 15 | plot_bset_shape(BasicSet("{[i,j]: 1 = 0}")) 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | from islplot.support import * 2 | from islpy import * 3 | 4 | import unittest 5 | from test import support 6 | 7 | class TestGetVertexCoordinates(unittest.TestCase): 8 | def setUp(self): 9 | return 10 | 11 | def tearDown(self): 12 | return 13 | 14 | def test_2d_set_a(self): 15 | bset = BasicSet("{[i,j]: 0 <= i <= 10 and j = 0}") 16 | c = bset_get_vertex_coordinates(bset) 17 | assert c == [[0.0, 0.0], [10.0, 0.0]] 18 | 19 | def test_2d_set_b(self): 20 | bset = BasicSet("{[i,j]: 0 <= i,j <= 10}") 21 | c = bset_get_vertex_coordinates(bset) 22 | assert c == [[0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0]] 23 | 24 | def test_2d_set_c(self): 25 | bset = BasicSet("{[i,j]: 0 <= i,j <= 10 and i + j < 2}") 26 | c = bset_get_vertex_coordinates(bset) 27 | assert c == [[0.0, 0.0], [0.0, 1.0], [1.0, 0.0]] 28 | 29 | def test_2d_set_d(self): 30 | bset = BasicSet("{[i,j]: 0 <= i,j <= 10 and i - j >= -4}") 31 | c = bset_get_vertex_coordinates(bset) 32 | assert c == [[0.0, 0.0], [0.0, 4.0], [6.0, 10.0], [10.0, 10.0], [10.0, 0.0]] 33 | 34 | def test_2d_set_with_non_integral_vertex(self): 35 | bset = BasicSet("{[i,j]: 0 <= i,j <= 9 and i - 2j >= -4}") 36 | c = bset_get_vertex_coordinates(bset) 37 | assert c == [[0.0, 0.0], [0.0, 2.0], [9.0, 6.5], [9.0, 0.0]] 38 | 39 | def test_2d_set_single_vertex(self): 40 | bset = BasicSet("{[2,2]}") 41 | c = bset_get_vertex_coordinates(bset) 42 | assert c == [[2.0, 2.0]] 43 | 44 | class Test_bset_get_faces(unittest.TestCase): 45 | def test_3d_empty(self): 46 | bset = BasicSet("{[i,j,k]: 1=0}") 47 | f = bset_get_faces(bset) 48 | assert f == [] 49 | 50 | def test_3d_point(self): 51 | bset = BasicSet("{[i,j,k]: i = j = k = 0}") 52 | f = bset_get_faces(bset) 53 | assert f == [[[0.0, 0.0, 0.0]]] 54 | 55 | def test_3d_line(self): 56 | bset = BasicSet("{[i,j,k]: i = j = 0 and 0 <= k <= 10}") 57 | f = bset_get_faces(bset) 58 | assert f == [[[0.0, 0.0, 0.0], [0.0, 0.0, 10.0]]] 59 | 60 | def test_3d_triangle(self): 61 | bset = BasicSet("{[i,j,k]: 0 <= i,j <= 10 and i + j < 2 and k = 0}") 62 | f = bset_get_faces(bset) 63 | assert f == [[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]] 64 | 65 | def test_3d_square(self): 66 | bset = BasicSet("{[i,j,k]: 0 <= i,k <= 10 and j = 2}") 67 | f = bset_get_faces(bset) 68 | assert f == [[[0.0, 2.0, 0.0], [10.0, 2.0, 0.0], [10.0, 2.0, 10.0], [0.0, 2.0, 10.0]]] 69 | 70 | def test_3d_pyramid(self): 71 | bset = BasicSet("{ [i, j, k] : 0 <= i,k,j and i + j + k <= 10}") 72 | f = bset_get_faces(bset) 73 | assert f == [[[0.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]], 74 | [[0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [0.0, 0.0, 10.0]], 75 | [[0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [0.0, 10.0, 0.0]], 76 | [[0.0, 0.0, 10.0], [10.0, 0.0, 0.0], [0.0, 10.0, 0.0]]] 77 | 78 | def test_3d_stretched_diamond(self): 79 | bset = BasicSet("{ [i, j, k] : 0 <= i,k,j and i + j < 4 and k < 4}") 80 | f = bset_get_faces(bset) 81 | assert f == [[[0.0, 0.0, 0.0], [0.0, 3.0, 0.0], [0.0, 3.0, 3.0], [0.0, 0.0, 3.0]], 82 | [[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 3.0, 0.0]], 83 | [[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [3.0, 0.0, 3.0], [0.0, 0.0, 3.0]], 84 | [[0.0, 0.0, 3.0], [3.0, 0.0, 3.0], [0.0, 3.0, 3.0]], 85 | [[0.0, 3.0, 0.0], [3.0, 0.0, 0.0], [3.0, 0.0, 3.0], [0.0, 3.0, 3.0]]] 86 | 87 | class Test_get_vertices_and_faces(unittest.TestCase): 88 | def test_3d_empty(self): 89 | bset = BasicSet("{[i,j,k]: 1=0}") 90 | v, f = get_vertices_and_faces(bset) 91 | assert v == [] 92 | assert f == [] 93 | 94 | def test_3d_point(self): 95 | bset = BasicSet("{[i,j,k]: i = j = k = 0}") 96 | v, f = get_vertices_and_faces(bset) 97 | assert v == [[0.0, 0.0, 0.0]] 98 | assert f == [[0]] 99 | 100 | def test_3d_line(self): 101 | bset = BasicSet("{[i,j,k]: i = j = 0 and 0 <= k <= 10}") 102 | v, f = get_vertices_and_faces(bset) 103 | assert v == [[0.0, 0.0, 0.0], [0.0, 0.0, 10.0]] 104 | assert f == [[0, 1]] 105 | 106 | def test_3d_triangle(self): 107 | bset = BasicSet("{[i,j,k]: 0 <= i,j <= 10 and i + j < 2 and k = 0}") 108 | v, f = get_vertices_and_faces(bset) 109 | assert v == [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]] 110 | assert f == [[0, 2, 1]] 111 | 112 | def test_3d_square(self): 113 | bset = BasicSet("{[i,j,k]: 0 <= i,k <= 10 and j = 2}") 114 | v, f = get_vertices_and_faces(bset) 115 | assert v == [[0.0, 2.0, 0.0], [0.0, 2.0, 10.0], [10.0, 2.0, 0.0], [10.0, 2.0, 10.0]] 116 | assert f == [[0, 2, 3, 1]] 117 | 118 | def test_3d_pyramid(self): 119 | bset = BasicSet("{ [i, j, k] : 0 <= i,k,j and i + j + k <= 10}") 120 | v, f = get_vertices_and_faces(bset) 121 | assert v == [[0.0, 0.0, 0.0], [0.0, 0.0, 10.0], [0.0, 10.0, 0.0], [10.0, 0.0, 0.0]] 122 | assert f == [[0, 2, 1], [0, 3, 1], [0, 3, 2], [1, 3, 2]] 123 | 124 | def test_3d_rational_vertices(self): 125 | bset = BasicSet("{ [i, j, k] : 0 <= i , j, k and i,k+2j <= 3 }") 126 | v, f = get_vertices_and_faces(bset) 127 | assert v == [[0.0, 0.0, 0.0], [0.0, 0.0, 3.0], [0.0, 1.5, 0.0], 128 | [3.0, 0.0, 0.0], [3.0, 0.0, 3.0], [3.0, 1.5, 0.0]] 129 | assert f == [[0, 2, 1], [0, 3, 4, 1], [0, 3, 5, 2], [1, 4, 5, 2], 130 | [3, 5, 4]] 131 | def test_3d_rational_vertices2(self): 132 | bset = BasicSet("{ [i0, i1, i2] : i0 >= 0 and i2 >= -i1 and i2 <= 15 - i0 - 2i1 and 2i1 >= 3 + i0 and 2i1 <= 15 + i0 and i2 <= -3 and i2 >= -15 }") 133 | f1 = bset_get_faces(bset) 134 | v, f = get_vertices_and_faces(bset) 135 | assert v == [[0.0, 3.0, -3.0], [0.0, 7.5, -7.5], [0.0, 7.5, -3.0], 136 | [1.5, 8.25, -3.0], [3.0, 3.0, -3.0], [5.0, 10.0, -10.0], 137 | [7.5, 5.25, -3.0], [9.0, 6.0, -6.0]] 138 | assert f == [[0, 2, 1], [0, 4, 6, 3, 2], [0, 4, 7, 5, 1], [1, 5, 3, 2], 139 | [3, 6, 7, 5], [4, 7, 6]] 140 | 141 | class Test_bset_get_points(unittest.TestCase): 142 | def test_scale(self): 143 | bset = BasicSet("{[i]: 0 <= i <= 1}") 144 | p = bset_get_points(bset, scale=10) 145 | assert p == [[0], [0.1]] 146 | 147 | def test_scale_2(self): 148 | bset = BasicSet("{[i]: 0 <= i <= 1}") 149 | p = bset_get_points(bset, scale=20) 150 | assert p == [[0], [0.05]] 151 | 152 | def test_cube(self): 153 | bset = BasicSet("{[i,j,k]: 0 <= i,j,k <= 2}") 154 | p = bset_get_points(bset) 155 | assert p == [[0, 0, 0], [0, 0, 1], [0, 0, 2], 156 | [0, 1, 0], [0, 1, 1], [0, 1, 2], 157 | [0, 2, 0], [0, 2, 1], [0, 2, 2], 158 | 159 | [1, 0, 0], [1, 0, 1], [1, 0, 2], 160 | [1, 1, 0], [1, 1, 1], [1, 1, 2], 161 | [1, 2, 0], [1, 2, 1], [1, 2, 2], 162 | 163 | [2, 0, 0], [2, 0, 1], [2, 0, 2], 164 | [2, 1, 0], [2, 1, 1], [2, 1, 2], 165 | [2, 2, 0], [2, 2, 1], [2, 2, 2]] 166 | 167 | def test_cube_only_hull(self): 168 | bset = BasicSet("{[i,j,k]: 0 <= i,j,k <= 2}") 169 | p = bset_get_points(bset, only_hull=True) 170 | assert p == [[0, 0, 0], [0, 0, 1], [0, 0, 2], 171 | [0, 1, 0], [0, 1, 1], [0, 1, 2], 172 | [0, 2, 0], [0, 2, 1], [0, 2, 2], 173 | 174 | [1, 0, 0], [1, 0, 1], [1, 0, 2], 175 | [1, 1, 0], [1, 1, 2], 176 | [1, 2, 0], [1, 2, 1], [1, 2, 2], 177 | 178 | [2, 0, 0], [2, 0, 1], [2, 0, 2], 179 | [2, 1, 0], [2, 1, 1], [2, 1, 2], 180 | [2, 2, 0], [2, 2, 1], [2, 2, 2]] 181 | 182 | class Test_get_rectangular_hull(unittest.TestCase): 183 | def test_triangular(self): 184 | bset = BasicSet("{[i,j]: 0 <= i,j and i <= 2 and j + i <= 4}") 185 | p = get_rectangular_hull(bset, offset=0) 186 | assert p.is_equal(Set("{ [i, j] : 0 <= i <= 2 and 0 <= j <= 4 }")) 187 | 188 | def test_triangular_offset(self): 189 | bset = BasicSet("{[i,j]: 0 <= i,j and i <= 2 and j + i <= 4}") 190 | p = get_rectangular_hull(bset, offset=2) 191 | assert p.is_equal(Set("{ [i, j] : -2 <= i <= 4 and -2 <= j <= 6 }")) 192 | 193 | class Test_sort_points(unittest.TestCase): 194 | def test_empty(self): 195 | res = sort_points([]) 196 | assert res == [] 197 | 198 | def test_single(self): 199 | bset = BasicSet("{[0,1,2]}") 200 | points = [] 201 | bset.foreach_point(points.append) 202 | points = sort_points(points) 203 | points = list(map(get_point_coordinates, points)) 204 | assert points == [[0,1,2]] 205 | 206 | def test_single(self): 207 | bset1 = BasicSet("{[x,y]: 0 <= x, y <= 1}") 208 | bset2 = BasicSet("{[x,y]: -1 <= x, y <= 0}") 209 | points = [] 210 | bset1.foreach_point(points.append) 211 | bset2.foreach_point(points.append) 212 | points = sort_points(points) 213 | points = list(map(get_point_coordinates, points)) 214 | assert points == [[-1, -1], [-1, 0], 215 | [0, -1], [0, 0], [0, 0], [0, 1], 216 | [1, 0], [1, 1]] 217 | 218 | 219 | if __name__ == '__main__': 220 | unittest.main() 221 | --------------------------------------------------------------------------------