diff --git a/.gitignore b/.gitignore index 733f62c7e5dfeaa94d34319c8f5980f9f2eba959..fd979af5008942c104f1d1a13e6f452bb1ec01bb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ MANIFEST dist setuptools*egg setuptools.pth +_build diff --git a/MANIFEST.in b/MANIFEST.in index 6eff45492b537714c2e91f91c2074b99ddb769b7..960240025cb887a8bdc6e91df496092106289fad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,14 @@ include LITERATURE include TODO include distribute_setup.py + +include README.rst + +include doc/*.rst +include doc/Makefile +include doc/*.py +include doc/conf.py +include doc/_static/*.css +include doc/_templates/*.html + +include test/*.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..48f56e5c05a66c445ed98b8811754653ac1633f4 --- /dev/null +++ b/README.rst @@ -0,0 +1,27 @@ +Pymbolic +======== + +Pymbolic is a small expression tree and symbolic manipulation library. Two +things set it apart from other libraries of its kind: + +* Users can easily write their own symbolic operations, simply by deriving +from the builtin visitor classes. +* Users can easily add their own symbolic entities to do calculations +with. + +Pymbolic currently understands regular arithmetic expressions, derivatives, +sparse polynomials, fractions, term substitution, expansion. It automatically +performs constant folding, and it can compile its expressions into Python +bytecode for fast(er) execution. + +If you are looking for a full-blown Computer Algebra System, look at +`sympy `_ or +`PyGinac `_. If you are looking for a +basic, small and extensible set of symbolic operations, pymbolic may +well be for you. + +Resources: + +* `download `_ (via the package index) +* `documentation `_ +* `source code via git `_ diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..668e7c82a2c11d5800679c56dac0f388fdc13ee0 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pymbolic.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pymbolic.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pymbolic" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pymbolic" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/_static/akdoc.css b/doc/_static/akdoc.css new file mode 100644 index 0000000000000000000000000000000000000000..e83c3b72c2b03cb6bfcd5ed746afa7dd18bbdbf6 --- /dev/null +++ b/doc/_static/akdoc.css @@ -0,0 +1,59 @@ +pre { + line-height: 100%; +} + +.footer { + background-color: #eee; +} + +body > div.container { + margin-top:10px; +} + +dd { + margin-left: 40px; +} + +tt.descname { + font-size: 100%; +} + +code { + color: rgb(51,51,51); +} + +h1 { + padding-bottom:5px; + border-bottom: 1px solid #ccc; +} + +h2 { + padding-bottom:1px; + border-bottom: 1px solid #ccc; +} + +h3 { + padding-bottom:1px; + border-bottom: 1px solid #ccc; +} + +.rubric { + font-size: 120%; + padding-bottom:1px; + border-bottom: 1px solid #ccc; +} + +.headerlink { + padding-left: 1ex; + padding-right: 1ex; +} + +a.headerlink:hover { + text-decoration: none; +} + +blockquote p { + font-size: 100%; + font-weight: normal; + line-height: normal; +}; diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html new file mode 100644 index 0000000000000000000000000000000000000000..0fed238faf1fc27457ad8672017577f055e7edf0 --- /dev/null +++ b/doc/_templates/layout.html @@ -0,0 +1,2 @@ +{% extends "!layout.html" %} +{% set css_files = css_files + ['_static/akdoc.css']%} diff --git a/doc/algorithms.rst b/doc/algorithms.rst new file mode 100644 index 0000000000000000000000000000000000000000..6ef962a332122a38fc5ff6e593930bb05a306fde --- /dev/null +++ b/doc/algorithms.rst @@ -0,0 +1,13 @@ +Algorithms +========== + +.. automodule:: pymbolic.algorithm + +.. autofunction:: integer_power +.. autofunction:: extended_euclidean +.. autofunction:: gcd +.. autofunction:: lcm +.. autofunction:: fft +.. autofunction:: ifft +.. autofunction:: sym_fft + diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..be301b2e91a730346943cc77695dbee98892f01c --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- +# +# pymbolic documentation build configuration file, created by +# sphinx-quickstart on Fri May 24 11:29:00 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +#import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode' + ] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pymbolic' +copyright = u'2013, Andreas Kloeckner' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +ver_dic = {} +execfile("../pymbolic/version.py", ver_dic) +version = ".".join(str(x) for x in ver_dic["VERSION"]) +# The full version, including alpha/beta/rc tags. +release = ver_dic["VERSION_TEXT"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +try: + import sphinx_bootstrap_theme +except: + from warnings import warn + warn("I would like to use the sphinx bootstrap theme, but can't find it.\n" + "'pip install sphinx_bootstrap_theme' to fix.") +else: + # Activate the theme. + html_theme = 'bootstrap' + html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() + + # Theme options are theme-specific and customize the look and feel of a theme + # further. For a list of options available for each theme, see the + # documentation. + html_theme_options = { + "navbar_fixed_top": "true", + "navbar_class": "navbar navbar-inverse", + "navbar_site_name": "Contents", + } + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pymbolicdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pymbolic.tex', u'pymbolic Documentation', + u'Andreas Kloeckner', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pymbolic', u'pymbolic Documentation', + [u'Andreas Kloeckner'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'pymbolic', u'pymbolic Documentation', + u'Andreas Kloeckner', 'pymbolic', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'http://docs.python.org/dev': None, + 'http://docs.scipy.org/doc/numpy/': None, + 'http://docs.makotemplates.org/en/latest/': None, + 'http://docs.sympy.org/dev/': None, + } + +autoclass_content = "both" diff --git a/doc/geometric-algebra.rst b/doc/geometric-algebra.rst new file mode 100644 index 0000000000000000000000000000000000000000..e52ec8839cbd50d1636933d1ce43b8992b01c9ee --- /dev/null +++ b/doc/geometric-algebra.rst @@ -0,0 +1,117 @@ +Geometric Algebra +================= + +.. automodule:: pymbolic.geometric_algebra + +Spaces +------ + +.. autoclass:: Space + + .. autoattribute:: dimensions + .. autoattribute:: is_orthogonal + .. autoattribute:: is_euclidean + +.. autofunction:: get_euclidean_space + +Multivectors +------------ + +.. autoclass:: MultiVector + + .. autoattribute:: mapper_method + + .. rubric:: More products + + .. automethod:: scalar_product + .. automethod:: x + .. automethod:: __pow__ + + .. rubric:: Unary operators + + .. automethod:: inv + .. automethod:: rev + .. automethod:: invol + .. automethod:: dual + .. automethod:: norm_squared + .. automethod:: __abs__ + .. autoattribute:: I + + .. rubric:: Comparisons + + :class:`MultiVector` objects have a truth value corresponding to whether + they have any blades with non-zero coefficients. They support testing + for (exact) equality. + + .. automethod:: zap_near_zeros + .. automethod:: close_to + + .. rubric:: Grade manipulation + + .. automethod:: gen_blades + .. automethod:: project + .. automethod:: all_grades + .. automethod:: get_pure_grade + .. automethod:: odd + .. automethod:: even + .. automethod:: as_scalar + .. automethod:: as_vector + +Example usage +------------- + +This first example demonstrates how to compute a cross product using +:class:`MultiVector`: + +.. doctest:: + + >>> import numpy as np + >>> import pymbolic.geometric_algebra as ga + >>> MV = ga.MultiVector + + >>> a = np.array([3.344, 1.2, -0.5]) + >>> b = np.array([7.4, 1.1, -2.0]) + >>> np.cross(a, b) + array([-1.85 , 2.988 , -5.2016]) + + >>> mv_a = MV(a) + >>> mv_b = MV(b) + >>> print -mv_a.I*(mv_a^mv_b) + -1.85*e0 + 2.988*e1 + -5.2016*e2 + +This simple example demonstrates how a complex number is simply a special +case of a :class:`MultiVector`: + +.. doctest:: + + >>> import numpy as np + >>> import pymbolic.geometric_algebra as ga + >>> MV = ga.MultiVector + >>> + >>> sp = ga.Space(metric_matrix=-np.eye(1)) + >>> sp + Space(['e0'], array([[-1.]])) + + >>> one = MV(1, sp) + >>> one + MultiVector({0: 1}, Space(['e0'], array([[-1.]]))) + >>> print one + 1 + >>> print one.I + 1*e0 + >>> print one.I ** 2 + -1.0 + + >>> print (3+5j)*(2+3j)/(3j) + (6.33333333333+3j) + >>> print (3+5*one.I)*(2+3*one.I)/(3*one.I) + 6.33333333333 + 3.0*e0 + +The following test demonstrates the use of the object and shows many useful +properties: + +.. literalinclude:: ../test/test_pymbolic.py + :start-after: START_GA_TEST + :end-before: END_GA_TEST + +.. vim: sw=4 diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..f793738300d479e3fa6b6c251d3f72602c2908ca --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,29 @@ +Welcome to pymbolic's documentation! +==================================== + +.. automodule:: pymbolic + +Pymbolic around the web +----------------------- + +* `download `_ (via the package index) +* `documentation `_ +* `source code via git `_ + +Contents +-------- + +.. toctree:: + :maxdepth: 2 + + primitives + utilities + algorithms + mappers + geometric-algebra + misc + +* :ref:`genindex` +* :ref:`modindex` + +.. vim: sw=4 diff --git a/doc/misc.rst b/doc/misc.rst new file mode 100644 index 0000000000000000000000000000000000000000..21f0ae197c92ada0acc3f1f50116240d2a73976c --- /dev/null +++ b/doc/misc.rst @@ -0,0 +1,69 @@ +Installation +============ + +This command should install :mod:`pymbolic`:: + + pip install pymbolic + +You may need to run this with :command:`sudo`. +If you don't already have `pip `_, +run this beforehand:: + + curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py + python get-pip.py + +For a more manual installation, download the source, unpack it, +and say:: + + python setup.py install + +User-visible Changes +==================== + +Version 2013.2 +-------------- + +.. note:: + + This version is currently under development. You can get snapshots from + Pymbolic's `git repository `_ + +* Add :mod:`pymbolic.geometric_algebra`. +* First documented version. + +.. _license: + +License +======= + +:mod:`pymbolic` is licensed to you under the MIT/X Consortium license: + +Copyright (c) 2008-13 Andreas Klöckner + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Frequently Asked Questions +========================== + +The FAQ is maintained collaboratively on the +`Wiki FAQ page `_. + diff --git a/doc/primitives.rst b/doc/primitives.rst new file mode 100644 index 0000000000000000000000000000000000000000..19f4d1c390d9293cadae745b5de9d26dd7255052 --- /dev/null +++ b/doc/primitives.rst @@ -0,0 +1,112 @@ +Primitives (Basic Objects) +========================== + +.. automodule:: pymbolic.primitives + +Expression base class +--------------------- + +.. autoclass:: Expression + + .. attribute:: mapper_method + + The :class:`pymbolic.mapper.Mapper` method called for objects of + this type. + + .. automethod:: stringifier + + .. automethod:: __eq__ + .. automethod:: __hash__ + .. automethod:: __repr__ + +Sums, products and such +----------------------- + +.. autoclass:: Variable + :undoc-members: + :members: mapper_method + +.. autoclass:: Call + :undoc-members: + :members: mapper_method + +.. autoclass:: Subscript + :undoc-members: + :members: mapper_method + +.. autoclass:: Lookup + :undoc-members: + :members: mapper_method + +.. autoclass:: Sum + :undoc-members: + :members: mapper_method + +.. autoclass:: Product + :undoc-members: + :members: mapper_method + +.. autoclass:: Quotient + :undoc-members: + :members: mapper_method + +.. autoclass:: FloorDiv + :undoc-members: + :members: mapper_method + +.. autoclass:: Remainder + :undoc-members: + :members: mapper_method + +.. autoclass:: Power + :undoc-members: + :members: mapper_method + +Comparisons and logic +--------------------- + +.. autoclass:: ComparisonOperator + :undoc-members: + :members: mapper_method + +.. autoclass:: LogicalNot + :undoc-members: + :members: mapper_method + +.. autoclass:: LogicalAnd + :undoc-members: + :members: mapper_method + +.. autoclass:: LogicalOr + :undoc-members: + :members: mapper_method + +.. autoclass:: If + :undoc-members: + :members: mapper_method + +Code generation helpers +----------------------- + +.. autoclass:: CommonSubexpression + :undoc-members: + :members: mapper_method + +Predicates, Constants +--------------------- + +.. autofunction:: is_zero +.. autofunction:: is_constant +.. autofunction:: register_constant_class +.. autofunction:: unregister_constant_class + +Interaction with :mod:`numpy` arrays +------------------------------------ + +:mod:`numpy.ndarray` instances are supported anywhere in an expression. +In particular, :mod:`numpy` object arrays are useful for capturing +vectors and matrices of :mod:`pymbolic` objects. + +.. autofunction:: make_sym_vector + +.. vim: sw=4 diff --git a/doc/upload-docs.sh b/doc/upload-docs.sh new file mode 100755 index 0000000000000000000000000000000000000000..aba31228f44670bf08893c721b8c2df6290df2ed --- /dev/null +++ b/doc/upload-docs.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +rsync --progress --verbose --archive --delete _build/html/* doc-upload:doc/pymbolic diff --git a/pymbolic/__init__.py b/pymbolic/__init__.py index 8c96c01b9f96cb0adcbb3832a5b8344e968612a6..e6b96df6c5f0432403656805ec327f70790f0acf 100644 --- a/pymbolic/__init__.py +++ b/pymbolic/__init__.py @@ -24,9 +24,101 @@ THE SOFTWARE. +__doc__ = """ +Pymbolic is a simple and extensible package for precise manipulation of +symbolic expressions in Python. It doesn't try to compete with :mod:`sympy` as +a computer algebra system. Pymbolic emphasizes providing an extensible +expression tree and a flexible, extensible way to manipulate it. -import parser -import compiler +A taste of :mod:`pymbolic` +-------------------------- + +Follow along on a simple example. Let's import :mod:`pymbolic` and create a symbol, +*x* in this case. + +.. doctest:: + + >>> import pymbolic as pmbl + + >>> x = pmbl.var("x") + >>> x + Variable('x') + +Next, let's create an expression using *x*: + +.. doctest:: + + >>> u = (x+1)**5 + >>> u + Power(Sum(Variable('x'), 1), 5) + >>> print u + (x + 1)**5 + +Note the two ways an expression can be printed, namely :func:`repr` and :func:`str`. +:mod:`pymbolic` purposefully distinguishes the two. + +:mod:`pymbolic` does not perform any algebraic manipulations on expressions +you put in. It has a few of those built in, but that's not really the point: + +.. doctest:: + + >>> print pmbl.differentiate(u, 'x') + 5*(x + 1)**4 + +Manipulating expressions +^^^^^^^^^^^^^^^^^^^^^^^^ + +The point is for you to be able to easily write so-called *mappers* to +manipulate expressions. Suppose we would like all sums replaced by +products: + +.. doctest:: + + >>> from pymbolic.mapper import IdentityMapper + >>> class MyMapper(IdentityMapper): + ... def map_sum(self, expr): + ... return pmbl.primitives.Product(expr.children) + ... + >>> print MyMapper()(u) + (x*1)**5 + +Custom Objects +^^^^^^^^^^^^^^ + +You can also easily define your own objects to use inside an expression: + +.. doctest:: + + >>> from pymbolic.primitives import Expression + >>> class FancyOperator(Expression): + ... def __init__(self, operand): + ... self.operand = operand + ... + ... def __getinitargs__(self): + ... return (self.operand,) + ... + ... mapper_method = "map_fancy_operator" + ... + >>> 17*FancyOperator(u) + Product(17, FancyOperator(Power(Sum(Variable('x'), 1), 5))) + +As a final example, we can now derive from *MyMapper* to multiply all +*FancyOperator* instances by 2. + +.. doctest:: + + >>> class MyMapper2(MyMapper): + ... def map_fancy_operator(self, expr): + ... return 2*FancyOperator(self.rec(expr.operand)) + ... + >>> MyMapper2()(FancyOperator(u)) + Product(2, FancyOperator(Power(Product(Variable('x'), 1), 5))) +""" + +from pymbolic.version import VERSION_TEXT as __version__ + +import pymbolic.parser +import pymbolic.compiler import pymbolic.mapper.evaluator import pymbolic.mapper.stringifier @@ -54,7 +146,7 @@ evaluate = pymbolic.mapper.evaluator.evaluate evaluate_kw = pymbolic.mapper.evaluator.evaluate_kw compile = pymbolic.compiler.compile substitute = pymbolic.mapper.substitutor.substitute -differentiate = pymbolic.mapper.differentiator.differentiate +diff = differentiate = pymbolic.mapper.differentiator.differentiate expand = pymbolic.mapper.expander.expand flatten = pymbolic.mapper.flattener.flatten @@ -62,7 +154,7 @@ flatten = pymbolic.mapper.flattener.flatten def simplify(x): - # FIXME: Not yet implemented + # FIXME: Not implemented return x def grad(expression, variables): @@ -98,31 +190,3 @@ class MatrixFunction: def __call__(self, x): import pylinear.array as num return num.array([[func(x) for func in flist ] for flist in self.FunctionList]) - - - - -if __name__ == "__main__": - import math - #ex = parse("0 + 4.3e3j * alpha * math.cos(x+math.pi)") + 5 - - #print ex - #print repr(parse("x+y")) - #print evaluate(ex, {"alpha":5, "math":math, "x":-math.pi}) - #compiled = compile(substitute(ex, {var("alpha"): 5})) - #print compiled(-math.pi) - #import cPickle as pickle - #pickle.dumps(compiled) - - #print hash(ex) - #print is_constant(ex) - #print substitute(ex, {"alpha": ex}) - #ex2 = parse("math.cos(x**2/x)") - #print ex2 - #print differentiate(ex2, parse("x")) - - x0 = parse("x[0]") - ex = parse("1-x[0]") - print differentiate(ex, x0) - #print expand(ex) - diff --git a/pymbolic/algorithm.py b/pymbolic/algorithm.py index b183c56a6f978514afae1aa5269a90ec7775606d..610426435521e3eee23b5b9007d98a25810765e9 100644 --- a/pymbolic/algorithm.py +++ b/pymbolic/algorithm.py @@ -29,11 +29,15 @@ from pytools import memoize def integer_power(x, n, one=1): - # http://c2.com/cgi/wiki?IntegerPowerAlgorithm + """Compute :math:`x^n` using only multiplications. + + See also the `C2 wiki `_. + """ + assert isinstance(n, int) if n < 0: - raise RuntimeError, "the integer power algorithm does not work for negative numbers" + raise RuntimeError("the integer power algorithm does not work for negative numbers") aux = one @@ -65,8 +69,10 @@ def lcm(q, r): def extended_euclidean(q, r): - """Return a tuple (p, a, b) such that p = aq + br, - where p is the greatest common divisor. + """Return a tuple *(p, a, b)* such that :math:`p = aq + br`, + where *p* is the greatest common divisor of *q* and *r*. + + See also `Wikipedia `_. """ import pymbolic.traits as traits @@ -113,14 +119,18 @@ def find_factors(N): def fft(x, sign=1, wrap_intermediate=lambda x: x): - """Computes the Fourier transform of x: + r"""Computes the Fourier transform of x: + + .. math:: + + F[x]_k = \sum_{j=0}^{n-1} z^{kj} x_j - F[x]_k = \sum_{j=0}^{n-1} z^{kj} x_j + where :math:`z = \exp(-2i\pi\operatorname{sign}/n)` and ``n == len(x)``. + Works for all positive *n*. - where z = exp(sign*-2j*pi/n) and n = len(x). + See also `Wikipedia `_. """ - # http://en.wikipedia.org/wiki/Cooley-Tukey_FFT_algorithm # revision 293076305, http://is.gd/1c7PI from math import pi @@ -157,10 +167,11 @@ def ifft(x, wrap_intermediate=lambda x:x): def sym_fft(x, sign=1): - """Perform an FFT on the numpy object array x. + """Perform a (symbolic) FFT on the :mod:`numpy` object array x. Remove near-zero floating point constants, insert - CommonSubexpression wrappers at opportune points. + :class:`pymbolic.primitives.CommonSubexpression` + wrappers at opportune points. """ from pymbolic.mapper import IdentityMapper, CSECachingMapperMixin @@ -204,7 +215,7 @@ def sym_fft(x, sign=1): def csr_matrix_multiply(S, x): - """Multiplies a scipy.sparse.csr_matrix S by an object-array vector x. + """Multiplies a :class:`scipy.sparse.csr_matrix` S by an object-array vector x. """ h, w = S.shape diff --git a/pymbolic/geometric_algebra.py b/pymbolic/geometric_algebra.py index 7d0a63c5eb5722b5c771b2429d68a63a31f77657..9c60b16685111f524e3a6697ca375f375cfd73cf 100644 --- a/pymbolic/geometric_algebra.py +++ b/pymbolic/geometric_algebra.py @@ -28,6 +28,13 @@ import numpy as np +__doc__ = """ + +.. versionadded:: 2013.2 +""" + + + # {{{ helpers @@ -158,7 +165,7 @@ class Space(object): @property @memoize_method def is_euclidean(self): - return (self.metric_matrix == np.eye(self.mmat.shape[0])).all() + return (self.metric_matrix == np.eye(self.metric_matrix.shape[0])).all() def blade_bits_to_str(self, bits): return "^".join( @@ -166,6 +173,15 @@ class Space(object): for bit_num, name in enumerate(self.basis_names) if bits & (1 << bit_num)) + def __repr__(self): + if self is get_euclidean_space(self.dimensions): + return "Space(%d)" % self.dimensions + elif self.is_euclidean: + return "Space(%r)" % self.basis_names + else: + return "Space(%r, %r)" % (self.basis_names, self.metric_matrix) + + @memoize def get_euclidean_space(n): """Return the canonical *n*-dimensional Euclidean :class:`Space`. @@ -278,17 +294,29 @@ class _ScalarProduct(_GAProduct): # {{{ multivector class MultiVector(object): - """An immutable multivector type. Implementation follows [DFM]. + r"""An immutable multivector type. Its implementation follows [DFM]. + It is pickleable, and not picky about what data is used as coefficients. + It supports :class:`pymbolic.primitives.Expression` objects of course, + but it can take just about any other scalar-ish coefficients. .. attribute:: data - A mapping from a basis vector bitmap indicating blades to coefficients. - (see [DFM], Chapter 19 for the idea and rationale) + A mapping from a basis vector bitmap (indicating blades) to coefficients. + (see [DFM], Chapter 19 for idea and rationale) + + .. attribute:: space - The object behaves much like :class:`sympy.galgebra.GA.MV`, especially - with respect to the supported operators. + See the following literature: - .. _ops_table: + [DFM] L. Dorst, D. Fontijne, and S. Mann, `Geometric Algebra for Computer + Science: An Object-Oriented Approach to Geometry `_. Morgan Kaufmann, 2010. + + [HS] D. Hestenes and G. Sobczyk, `Clifford Algebra to Geometric Calculus: A + Unified Language for Mathematics and Physics `_. Springer, 1987. + + The object behaves much like the corresponding + :class:`sympy.galgebra.GA.MV` object in :mod:`sympy`, especially with + respect to the supported operators: .. csv-table:: :header: Operation, Result @@ -296,13 +324,11 @@ class MultiVector(object): ``A+B``, Sum of multivectors ``A-B``, Difference of multivectors - ``A*B``, Geometric product - ``A^B``, Outer product of multivectors - ``A|B``, Inner product of multivectors - `A<>B``, Right contraction of multivectors - - Table :ref:`1 `. :class:`Multi operations + ``A*B``, Geometric product :math:`AB` + ``A^B``, Outer product :math:`A\wedge B` of multivectors + ``A|B``, Inner product :math:`A\cdot B` of multivectors + ``A<>B``, Right contraction of multivectors .. warning :: @@ -312,12 +338,6 @@ class MultiVector(object): and then geometric. In other words: Use parentheses everywhere. - - [DFM] L. Dorst, D. Fontijne, and S. Mann, Geometric Algebra for Computer - Science: An Object-Oriented Approach to Geometry. Morgan Kaufmann, 2010. - - [HS] D. Hestenes and G. Sobczyk, Clifford Algebra to Geometric Calculus: A - Unified Language for Mathematics and Physics. Springer, 1987. """ # {{{ construction @@ -325,16 +345,17 @@ class MultiVector(object): def __init__(self, data, space=None): """ :arg data: This may be one of the following: - 1) a :class:`numpy.ndarray`, which will be turned into a grade-1 multivector, - 2) a mapping from tuples of basis indices (together indicating a blade, - order matters and will be mapped to 'normalized' blades) to coefficients, - 3) an array as described in :attr:`data`, - 4) a scalar--where everything that doesn't fall into the above cases - is viewed as a scalar. + + * a :class:`numpy.ndarray`, which will be turned into a grade-1 multivector, + * a mapping from tuples of basis indices (together indicating a blade, + order matters and will be mapped to 'normalized' blades) to coefficients, + * an array as described in :attr:`data`, + * a scalar--where everything that doesn't fall into the above cases + is viewed as a scalar. :arg space: A :class:`Space` instance. If *None* or an integer, :func:`get_euclidean_space` is called to obtain a default space with the right number of dimensions for *data*. Note: dimension guessing only - works when :class:`numpy.ndarrays` are being passed for *data*. + works when a :class:`numpy.ndarray` is being passed for *data*. """ dimensions = None @@ -412,6 +433,9 @@ class MultiVector(object): return " + ".join(terms) + def __repr__(self): + return "MultiVector(%s, %r)" % (self.data, self.space) + # }}} # {{{ additive operators @@ -534,18 +558,33 @@ class MultiVector(object): ._generic_product(self, _RightContractionProduct) def scalar_product(self, other): + r"""Return the scalar product, as a scalar, not a :class:`MultiVector`. + + Often written :math:`A*B`. + """ + if not isinstance(other, MultiVector): other = MultiVector(other, self.space) return self._generic_product(other, _ScalarProduct).as_scalar() def x(self, other): - """Return the commutator product. + r"""Return the commutator product. + + See (1.1.55) in [HS]. - See (1.55) in [HS]. + Often written :math:`A\times B`. """ return (self*other - other*self)/2 + def __pow__(self, other): + """Return *self* to the integer power *other*.""" + + other = int(other) + + from pymbolic.algorithm import integer_power + return integer_power(self, other, one=MultiVector({0:1}, self.space)) + def __truediv__(self, other): """Return ``self*(1/other)``. """ @@ -561,10 +600,16 @@ class MultiVector(object): return other * self.inv() + __div__ = __truediv__ + # }}} + # {{{ unary operations + def inv(self): """Return the *multiplicative inverse* of the blade *self*. + + Often written :math:`A^{-1}`. """ nsqr = self.norm_squared() @@ -581,7 +626,7 @@ class MultiVector(object): (bits, coeff), = self.data.iteritems() - # (1.54) in [HS] + # (1.1.54) in [HS] grade = bit_count(bits) if grade*(grade-1)//2 % 2: coeff = -coeff @@ -591,8 +636,10 @@ class MultiVector(object): return MultiVector({bits: coeff}, self.space) def rev(self): - """Return the *reverse* of *self*, i.e. the multivector obtained by reversing + r"""Return the *reverse* of *self*, i.e. the multivector obtained by reversing the order of all component blades. + + Often written :math:`A^\dagger`. """ new_data = {} for bits, coeff in self.data.iteritems(): @@ -605,8 +652,10 @@ class MultiVector(object): return MultiVector(new_data, self.space) def invol(self): - """Return the grade involution (see Section 2.9.5 of [DFM]), i.e. + r"""Return the grade involution (see Section 2.9.5 of [DFM]), i.e. all odd-grade blades have their signs flipped. + + Often written :math:`\widehat A`. """ new_data = {} for bits, coeff in self.data.iteritems(): @@ -619,7 +668,10 @@ class MultiVector(object): return MultiVector(new_data, self.space) def dual(self): - """Return the dual of *self*, see (2.26) in [HS].""" + r"""Return the dual of *self*, see (1.2.26) in [HS]. + + Often written :math:`\widetilde A`. + """ return self | self.I.rev() @@ -631,8 +683,12 @@ class MultiVector(object): @property def I(self): + """Return the pseudoscalar associated with this object's :class:`Space`. + """ return MultiVector({2**self.space.dimensions-1: 1}, self.space) + # }}} + # {{{ comparisons def __nonzero__(self): @@ -648,7 +704,9 @@ class MultiVector(object): return not self.__eq__(other) def zap_near_zeros(self, tol=None): - # FIXME: Should use norm (or something) of self for tol. + """Remove blades whose coefficient is close to zero + relative to the norm of *self*. + """ if tol is None: tol = 1e-13 @@ -668,6 +726,9 @@ class MultiVector(object): # {{{ grade manipulation def gen_blades(self, grade=None): + """Generate all blades in *self*, optionally only those of a specific *grade*. + """ + if grade is None: for bits, coeff in self.data.iteritems(): yield MultiVector({bits: coeff}, self.space) @@ -676,14 +737,23 @@ class MultiVector(object): if bit_count(bits) == grade: yield MultiVector({bits: coeff}, self.space) - def project(self, grade): + def project(self, r): + r"""Return a new multivector containing only the blades of grade *r*. + + Often written :math:`\langle A\rangle_r`. + """ new_data = {} for bits, coeff in self.data.iteritems(): - if bit_count(bits) == grade: + if bit_count(bits) == r: new_data[bits] = coeff return MultiVector(new_data, self.space) + def all_grades(self): + """Return a :class:`set` of grades occurring in *self*.""" + + return set(bit_count(bits) for bits, coeff in self.data.iteritems()) + def get_pure_grade(self): """If *self* only has components of a single grade, return that as an integer. Otherwise, return *None*. @@ -731,8 +801,18 @@ class MultiVector(object): return result - def as_vector(self): - result = [0] * self.space.dimensions + def as_vector(self, dtype=None): + """Return a :mod:`numpy` vector corresponding to the grade-1 + :class:`MultiVector` *self*. + + If *self* is not grade-1, :exc:`ValueError` is raised. + """ + + if dtype is not None: + result = np.zeros(self.space.dimensions, dtype=dtype) + else: + result = [0] * self.space.dimensions + log_table = dict((2**i, i) for i in xrange(self.space.dimensions)) try: for bits, coeff in self.data.iteritems(): @@ -740,7 +820,10 @@ class MultiVector(object): except KeyError: raise ValueError("multivector is not a purely grade-1") - return np.array(result) + if dtype is not None: + return result + else: + return np.array(result) # }}} diff --git a/pymbolic/primitives.py b/pymbolic/primitives.py index fc2413ce154cbb0c8fe7c8daceff3ac2ab60d608..d379c87a936b302c84aca2cf7ad0476df5750eae 100644 --- a/pymbolic/primitives.py +++ b/pymbolic/primitives.py @@ -28,7 +28,8 @@ import traits class Expression(object): - """An evaluatable part of a mathematical expression. + """Superclass for parts of a mathematical expression. Overrides operators + to implicitly construct :class:`Sum`, :class:`Product` and other expressions. Expression objects are immutable. """ @@ -185,6 +186,10 @@ class Expression(object): return evaluate_to_float(self) def stringifier(self): + """Return a :class:`pymbolic.mapper.Mapper` class used to yield + a human-readable representation of *self*. Usually a subclass + of :class:`pymbolic.mapper.stringifier.StringifyMapper`. + """ from pymbolic.mapper.stringifier import StringifyMapper return StringifyMapper @@ -193,6 +198,9 @@ class Expression(object): return self.stringifier()()(self, PREC_NONE) def __repr__(self): + """Provides a default :func:`repr` based on + the Python pickling interface :meth:`__getinitargs__`. + """ initargs_str = ", ".join(repr(i) for i in self.__getinitargs__()) return "%s(%s)" % (self.__class__.__name__, initargs_str) @@ -201,10 +209,10 @@ class Expression(object): def __eq__(self, other): """Provides equality testing with quick positive and negative paths - based on L{id} and L{__hash__}(). + based on :func:`id` and :meth:`__hash__`. Subclasses should generally not override this method, but instead - provide an implementation of L{is_equal}. + provide an implementation of :meth:`is_equal`. """ if self is other: return True @@ -220,7 +228,7 @@ class Expression(object): """Provides caching for hash values. Subclasses should generally not override this method, but instead - provide an implementation of L{get_hash}. + provide an implementation of :meth:`get_hash`. """ try: return self.hash_value @@ -275,6 +283,9 @@ class Leaf(AlgebraicLeaf): class Variable(Leaf): + """ + .. attribute:: name + """ def __init__(self, name): self.name = name @@ -318,7 +329,18 @@ class FunctionSymbol(AlgebraicLeaf): # {{{ structural primitives class Call(AlgebraicLeaf): + """A function invocation. + + .. attribute:: function + .. attribute:: parameters + """ + def __init__(self, function, parameters): + """ + :arg function: A :class:`Expression` that evaluates to a function. + :arg parameters: A :class:`tuple` of positional paramters. + """ + self.function = function self.parameters = parameters @@ -341,6 +363,11 @@ class Call(AlgebraicLeaf): class Subscript(AlgebraicLeaf): + """An array subscript. + + .. attribute:: aggregate + .. attribute:: index + """ def __init__(self, aggregate, index): self.aggregate = aggregate self.index = index @@ -354,6 +381,10 @@ class Subscript(AlgebraicLeaf): class Lookup(AlgebraicLeaf): + """Access to an attribute of an *aggregate*, such as an + attribute of a class. + """ + def __init__(self, aggregate, name): self.aggregate = aggregate self.name = name @@ -368,6 +399,12 @@ class Lookup(AlgebraicLeaf): # {{{ arithmetic primitives class Sum(Expression): + """ + .. attribute:: children + + A :class:`tuple`. + """ + def __init__(self, children): assert isinstance(children, tuple) @@ -419,6 +456,12 @@ class Sum(Expression): class Product(Expression): + """ + .. attribute:: children + + A :class:`tuple`. + """ + def __init__(self, children): assert isinstance(children, tuple) self.children = children @@ -482,6 +525,11 @@ class QuotientBase(Expression): class Quotient(QuotientBase): + """ + .. attribute:: numerator + .. attribute:: denominator + """ + def is_equal(self, other): from pymbolic.rational import Rational return isinstance(other, (Rational, Quotient)) \ @@ -494,18 +542,33 @@ class Quotient(QuotientBase): class FloorDiv(QuotientBase): + """ + .. attribute:: numerator + .. attribute:: denominator + """ + mapper_method = intern("map_floor_div") class Remainder(QuotientBase): + """ + .. attribute:: numerator + .. attribute:: denominator + """ + mapper_method = intern("map_remainder") class Power(Expression): + """ + .. attribute:: base + .. attribute:: exponent + """ + def __init__(self, base, exponent): self.base = base self.exponent = exponent @@ -520,11 +583,22 @@ class Power(Expression): # {{{ comparisons, logic, conditionals class ComparisonOperator(Expression): - """Note: comparisons are not implicitly constructed by comparing - Expression objects. + """ + .. attribute:: left + .. attribute:: operator + .. attribute:: right + + .. note:: + + Unlike other expressions, comparisons are not implicitly constructed by + comparing :class:`Expression` objects. """ def __init__(self, left, operator, right): + """ + :arg operator: One of ``[">", ">=", "==", "!=", "<", "<="]``. + """ + self.left = left self.right = right if not operator in [">", ">=", "==", "!=", "<", "<="]: @@ -542,7 +616,11 @@ class ComparisonOperator(Expression): class BooleanExpression(Expression): pass -class LogcialNot(BooleanExpression): +class LogicalNot(BooleanExpression): + """ + .. attribute:: child + """ + def __init__(self, child): self.child = child @@ -555,6 +633,12 @@ class LogcialNot(BooleanExpression): class LogicalOr(BooleanExpression): + """ + .. attribute:: children + + A :class:`tuple`. + """ + def __init__(self, children): assert isinstance(children, tuple) @@ -569,6 +653,12 @@ class LogicalOr(BooleanExpression): class LogicalAnd(BooleanExpression): + """ + .. attribute:: children + + A :class:`tuple`. + """ + def __init__(self, children): assert isinstance(children, tuple) @@ -583,6 +673,11 @@ class LogicalAnd(BooleanExpression): class If(Expression): + """ + .. attribute:: condition + .. attribute:: then + .. attribute:: else_ + """ def __init__(self, criterion, then, else_): self.condition = criterion self.then = then @@ -702,6 +797,14 @@ class Vector(Expression): class CommonSubexpression(Expression): + """A helper for code generation and caching. Denotes a subexpression that + should only be evaluated once. If, in code generation, it is assigned to + a variable, a name starting with :attr:`prefix` should be used. + + .. attribute:: child + .. attribute:: prefix + """ + def __init__(self, child, prefix=None): self.child = child self.prefix = prefix @@ -1013,7 +1116,7 @@ def make_common_subexpression(field, prefix=None): def make_sym_vector(name, components): """Return an object array of *components* subscripted - :class:`Field` instances. + :class:`Variable` instances. :param components: The number of components in the vector. """ diff --git a/pymbolic/version.py b/pymbolic/version.py new file mode 100644 index 0000000000000000000000000000000000000000..a5ec03cd8c5a763741c58ab7dadbcb8077e99d41 --- /dev/null +++ b/pymbolic/version.py @@ -0,0 +1,3 @@ +VERSION = (2013, 2) +VERSION_STATUS = "" +VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS diff --git a/setup.py b/setup.py index ff1fabd4d15b811029eed8afbcb902bf1eb8c674..137ef886804f41a565584be734d90706b89cc267 100644 --- a/setup.py +++ b/setup.py @@ -6,29 +6,19 @@ distribute_setup.use_setuptools() from setuptools import setup -setup(name="pymbolic", - version="2013.2", - description="A package for symbolic computation", - long_description=""" - Pymbolic is a small symbolic manipulation library. Two things set it apart - from other libraries of its kind: - - * Users can easily write their own symbolic operations, simply by deriving - from the builtin visitor classes. - * Users can easily add their own symbolic entities to do calculations - with. +ver_dic = {} +version_file = open("pymbolic/version.py") +try: + version_file_contents = version_file.read() +finally: + version_file.close() - Pymbolic currently understands regular arithmetic expressions, derivatives, - sparse polynomials, fractions, term substitution, expansion. It automatically - performs constant folding, and it can compile its expressions into Python - bytecode for fast(er) execution. +exec(compile(version_file_contents, "pyopencl/version.py", 'exec'), ver_dic) - If you are looking for a full-blown Computer Algebra System, look at - `sympy `_ or - `PyGinac `_. If you are looking for a - basic, small and extensible set of symbolic operations, pymbolic may - well be for you. - """, +setup(name="pymbolic", + version=ver_dic["VERSION_TEXT"], + description="A package for symbolic computation", + long_description=open("README.rst").read(), classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index 43cf32c5b75d8ea61e8380b02923e6778e199a89..684fc836defb5e1b5c2f48b88e38cd01eca798de 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -201,8 +201,11 @@ def test_structure_preservation(): @pytest.mark.parametrize("dims", [2,3,4,5]) +# START_GA_TEST def test_geometric_algebra(dims): - np = pytest.importorskip("numpy") + pytest.importorskip("numpy") + + import numpy as np from pymbolic.geometric_algebra import MultiVector as MV vec1 = MV(np.random.randn(dims)) @@ -242,8 +245,8 @@ def test_geometric_algebra(dims): a*(b*c)) assert ((a^b)^c).close_to( a^(b^c)) - assert ((a*b)*c).close_to( - a*(b*c)) + #assert ((a|b)|c).close_to( + #a|(b|c)) # scalar product assert ( (c*b).project(0) ) .close_to( b.scalar_product(c) ) @@ -277,7 +280,7 @@ def test_geometric_algebra(dims): assert (b^c).rev() .close_to( (c.rev() ^ b.rev()) ) # dual properties - # (2.26) in [HS] + # (1.2.26) in [HS] assert c.dual() .close_to( c|c.I.rev() ) assert c.dual() .close_to( c*c.I.rev() ) @@ -287,10 +290,11 @@ def test_geometric_algebra(dims): # commutator properties - # Jacobi identity (1.56c) in [HS] + # Jacobi identity (1.1.56c) in [HS] assert ( a.x(b.x(c)) + b.x(c.x(a)) + c.x(a.x(b)) ).close_to(0) # (1.57) in [HS] assert a.x(b*c) .close_to( a.x(b)*c + b*a.x(c)) +# END_GA_TEST