Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • isuruf/pymbolic
  • inducer/pymbolic
  • xywei/pymbolic
  • wence-/pymbolic
  • kaushikcfd/pymbolic
  • fikl2/pymbolic
  • zweiner2/pymbolic
7 results
Show changes
Commits on Source (829)
# https://editorconfig.org/
# https://github.com/editorconfig/editorconfig-vim
# https://github.com/editorconfig/editorconfig-emacs
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_size = 4
[*.rst]
indent_size = 4
[*.cpp]
indent_size = 2
[*.hpp]
indent_size = 2
# There may be one in doc/
[Makefile]
indent_style = tab
# https://github.com/microsoft/vscode/issues/1679
[*.md]
trim_trailing_whitespace = false
version: 2
updates:
# Set update schedule for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# vim: sw=4
name: Gitlab mirror
on:
push:
branches:
- main
jobs:
autopush:
name: Automatic push to gitlab.tiker.net
if: startsWith(github.repository, 'inducer/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
mirror_github_to_gitlab
env:
GITLAB_AUTOPUSH_KEY: ${{ secrets.GITLAB_AUTOPUSH_KEY }}
# vim: sw=4
name: CI
on:
push:
branches:
- main
pull_request:
paths-ignore:
- 'doc/*.rst'
schedule:
- cron: '17 3 * * 0'
concurrency:
group: ${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
jobs:
typos:
name: Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@master
ruff:
name: Ruff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
- name: "Main Script"
run: |
pip install ruff
ruff check
pylint:
name: Pylint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: "Main Script"
run: |
EXTRA_INSTALL="numpy sympy scipy pexpect"
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
build_py_project_in_venv
# https://github.com/inducer/pymbolic/pull/66#issuecomment-950371315
pip install symengine || true
run_pylint pymbolic test/test_*.py
mypy:
name: Mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: "Main Script"
run: |
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
build_py_project_in_venv
pip install -e .[test]
python -m pip install mypy numpy
./run-mypy.sh
pytest:
name: Pytest on Py${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.12", "3.x"]
steps:
- uses: actions/checkout@v4
-
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: "Main Script"
run: |
EXTRA_INSTALL="numpy sympy pexpect"
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
build_py_project_in_venv
# https://github.com/inducer/pymbolic/pull/66#issuecomment-950371315
pip install symengine || true
test_py_project
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: "Main Script"
run: |
EXTRA_INSTALL="numpy sympy"
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
build_py_project_in_venv
build_docs
downstream_tests:
strategy:
matrix:
downstream_project: [loopy, pytential, pytato]
fail-fast: false
name: Tests for downstream project ${{ matrix.downstream_project }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Main Script"
env:
DOWNSTREAM_PROJECT: ${{ matrix.downstream_project }}
run: |
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
test_downstream "$DOWNSTREAM_PROJECT"
# vim: sw=4
......@@ -8,4 +8,8 @@ build
MANIFEST
dist
setuptools*egg
setuptools*tar.gz
setuptools.pth
_build
.cache
Python 3:
script: |
PY_EXE=python3
# pytest tries to import this, but it doesn't find symengine
rm pymbolic/interop/symengine.py
EXTRA_INSTALL="numpy sympy pexpect"
curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh
. ./build-and-test-py-project.sh
tags:
- python3
- maxima
except:
- tags
artifacts:
reports:
junit: test/pytest.xml
Python 3 Conda:
script: |
CONDA_ENVIRONMENT=.test-py3.yml
curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh
. ./build-and-test-py-project-within-miniconda.sh
tags:
- linux
except:
- tags
artifacts:
reports:
junit: test/pytest.xml
Pylint:
script:
- EXTRA_INSTALL="numpy sympy symengine scipy pexpect"
- PY_EXE=python3
- curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-pylint.sh
- ". ./prepare-and-run-pylint.sh pymbolic test/test_*.py"
tags:
- python3
except:
- tags
Mypy:
script: |
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
build_py_project_in_venv
pip install -e .[test]
python -m pip install mypy numpy
./run-mypy.sh
tags:
- python3
except:
- tags
Documentation:
script:
- EXTRA_INSTALL="numpy sympy"
- curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-docs.sh
- ". ./build-docs.sh"
tags:
- linux
Ruff:
script: |
pipx install ruff
ruff check
tags:
- docker-runner
except:
- tags
Downstream:
parallel:
matrix:
- DOWNSTREAM_PROJECT: [loopy, pytential, pytato]
tags:
- large-node
- "docker-runner"
script: |
curl -L -O https://tiker.net/ci-support-v0
. ./ci-support-v0
test_downstream "$DOWNSTREAM_PROJECT"
- arg: ignored-modules
val:
- symengine
name: py3
channels:
- conda-forge
- defaults
dependencies:
- conda-forge::numpy
- conda-forge::sympy
- python
- python-symengine
# - pexpect
# - maxima
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Kloeckner"
given-names: "Andreas"
orcid: "https://orcid.org/0000-0003-1228-519X"
- family-names: "Wala"
given-names: "Matt"
- family-names: "Fernando"
given-names: "Isuru"
- family-names: "Kulkarni"
given-names: "Kaushik"
- family-names: "Fikl"
given-names: "Alex"
- family-names: "Weiner"
given-names: "Zach"
- family-names: "Kempf"
given-names: "Dominic"
- family-names: "Ham"
given-names: "David A."
- family-names: "Mitchell"
given-names: "Lawrence"
- family-names: "Wilcox"
given-names: "Lucas C"
- family-names: "Diener"
given-names: "Matthias"
- family-names: "Kapyshin"
given-names: "Pavlo"
- family-names: "Raksi"
given-names: "Reno"
- family-names: "Gibson"
given-names: "Thomas H."
title: "pymbolic"
version: 2022.1
doi: 10.5281/zenodo.6533945
date-released: 2022-05-08
url: "https://github.com/inducer/pymbolic"
license: MIT
pymbolic is licensed to you under the MIT/X Consortium license:
Copyright (c) 2009-16 Andreas Klöckner and Contributors.
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.
include LITERATURE
include TODO
include distribute_setup.py
include LICENSE
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
Pymbolic: Easy Expression Trees and Term Rewriting
==================================================
.. image:: https://gitlab.tiker.net/inducer/pymbolic/badges/main/pipeline.svg
:alt: Gitlab Build Status
:target: https://gitlab.tiker.net/inducer/pymbolic/commits/main
.. image:: https://github.com/inducer/pymbolic/workflows/CI/badge.svg?branch=main&event=push
:alt: Github Build Status
:target: https://github.com/inducer/pymbolic/actions?query=branch%3Amain+workflow%3ACI+event%3Apush
.. image:: https://badge.fury.io/py/pymbolic.png
:alt: Python Package Index Release Page
:target: https://pypi.org/project/pymbolic/
.. image:: https://zenodo.org/badge/2016193.svg
:alt: Zenodo DOI for latest release
:target: https://zenodo.org/badge/latestdoi/2016193
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 <https://pypi.org/project/sympy/>`__ or
`PyGinac <https://pyginac.sourceforge.net/>`__. If you are looking for a
basic, small and extensible set of symbolic operations, pymbolic may
well be for you.
Resources:
* `PyPI package <https://pypi.org/project/pymbolic/>`__
* `Documentation <https://documen.tician.de/pymbolic/>`__
* `Source code (GitHub) <https://github.com/inducer/pymbolic>`__
#!python
"""Bootstrap distribute installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from distribute_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import os
import sys
import time
import fnmatch
import tempfile
import tarfile
from distutils import log
try:
from site import USER_SITE
except ImportError:
USER_SITE = None
try:
import subprocess
def _python_cmd(*args):
args = (sys.executable,) + args
return subprocess.call(args) == 0
except ImportError:
# will be used for python 2.3
def _python_cmd(*args):
args = (sys.executable,) + args
# quoting arguments if windows
if sys.platform == 'win32':
def quote(arg):
if ' ' in arg:
return '"%s"' % arg
return arg
args = [quote(arg) for arg in args]
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
DEFAULT_VERSION = "0.6.4"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_PKG_INFO = """\
Metadata-Version: 1.0
Name: setuptools
Version: 0.6c9
Summary: xxxx
Home-page: xxx
Author: xxx
Author-email: xxx
License: xxx
Description: xxx
"""
def _install(tarball):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# installing
log.warn('Installing Distribute')
assert _python_cmd('setup.py', 'install')
finally:
os.chdir(old_wd)
def _build_egg(tarball, to_dir):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# building an egg
log.warn('Building a Distribute egg in %s', to_dir)
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
# returning the result
for file in os.listdir(to_dir):
if fnmatch.fnmatch(file, 'distribute-%s*.egg' % DEFAULT_VERSION):
return os.path.join(to_dir, file)
raise IOError('Could not build the egg.')
finally:
os.chdir(old_wd)
def _do_download(version, download_base, to_dir, download_delay):
tarball = download_setuptools(version, download_base,
to_dir, download_delay)
egg = _build_egg(tarball, to_dir)
sys.path.insert(0, egg)
import setuptools
setuptools.bootstrap_install_from = egg
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, download_delay=15):
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
was_imported = 'pkg_resources' in sys.modules or \
'setuptools' in sys.modules
try:
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
fake_setuptools()
raise ImportError
except ImportError:
return _do_download(version, download_base, to_dir, download_delay)
try:
pkg_resources.require("distribute>="+version)
return
except pkg_resources.VersionConflict, e:
if was_imported:
print >>sys.stderr, (
"The required version of distribute (>=%s) is not available,\n"
"and can't be installed while this script is running. Please\n"
"install a more recent version first, using\n"
"'easy_install -U distribute'."
"\n\n(Currently using %r)") % (version, e.args[0])
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return _do_download(version, download_base, to_dir,
download_delay)
except pkg_resources.DistributionNotFound:
return _do_download(version, download_base, to_dir,
download_delay)
finally:
_create_fake_setuptools_pkg_info(to_dir)
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, delay=15):
"""Download distribute from a specified location and return its filename
`version` should be a valid distribute version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
"""
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
import urllib2
tgz_name = "distribute-%s.tar.gz" % version
url = download_base + tgz_name
saveto = os.path.join(to_dir, tgz_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
log.warn("Downloading %s", url)
src = urllib2.urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = src.read()
dst = open(saveto, "wb")
dst.write(data)
finally:
if src:
src.close()
if dst:
dst.close()
return os.path.realpath(saveto)
def _patch_file(path, content):
"""Will backup the file then patch it"""
existing_content = open(path).read()
if existing_content == content:
# already patched
log.warn('Already patched.')
return False
log.warn('Patching...')
_rename_path(path)
f = open(path, 'w')
try:
f.write(content)
finally:
f.close()
return True
def _same_content(path, content):
return open(path).read() == content
def _rename_path(path):
new_name = path + '.OLD.%s' % time.time()
log.warn('Renaming %s into %s', path, new_name)
try:
from setuptools.sandbox import DirectorySandbox
def _violation(*args):
pass
DirectorySandbox._violation = _violation
except ImportError:
pass
os.rename(path, new_name)
return new_name
def _remove_flat_installation(placeholder):
if not os.path.isdir(placeholder):
log.warn('Unkown installation at %s', placeholder)
return False
found = False
for file in os.listdir(placeholder):
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
found = True
break
if not found:
log.warn('Could not locate setuptools*.egg-info')
return
log.warn('Removing elements out of the way...')
pkg_info = os.path.join(placeholder, file)
if os.path.isdir(pkg_info):
patched = _patch_egg_dir(pkg_info)
else:
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
if not patched:
log.warn('%s already patched.', pkg_info)
return False
# now let's move the files out of the way
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
element = os.path.join(placeholder, element)
if os.path.exists(element):
_rename_path(element)
else:
log.warn('Could not find the %s element of the '
'Setuptools distribution', element)
return True
def _after_install(dist):
log.warn('After install bootstrap.')
placeholder = dist.get_command_obj('install').install_purelib
_create_fake_setuptools_pkg_info(placeholder)
def _create_fake_setuptools_pkg_info(placeholder):
if not placeholder or not os.path.exists(placeholder):
log.warn('Could not find the install location')
return
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
setuptools_file = 'setuptools-0.6c9-py%s.egg-info' % pyver
pkg_info = os.path.join(placeholder, setuptools_file)
if os.path.exists(pkg_info):
log.warn('%s already exists', pkg_info)
return
log.warn('Creating %s', pkg_info)
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
pth_file = os.path.join(placeholder, 'setuptools.pth')
log.warn('Creating %s', pth_file)
f = open(pth_file, 'w')
try:
f.write(os.path.join(os.curdir, setuptools_file))
finally:
f.close()
def _patch_egg_dir(path):
# let's check if it's already patched
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
if os.path.exists(pkg_info):
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
log.warn('%s already patched.', pkg_info)
return False
_rename_path(path)
os.mkdir(path)
os.mkdir(os.path.join(path, 'EGG-INFO'))
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
return True
def _before_install():
log.warn('Before install bootstrap.')
fake_setuptools()
def _under_prefix(location):
if 'install' not in sys.argv:
return True
args = sys.argv[sys.argv.index('install')+1:]
for index, arg in enumerate(args):
for option in ('--root', '--prefix'):
if arg.startswith('%s=' % option):
top_dir = arg.split('root=')[-1]
return location.startswith(top_dir)
elif arg == option:
if len(args) > index:
top_dir = args[index+1]
return location.startswith(top_dir)
elif option == '--user' and USER_SITE is not None:
return location.startswith(USER_SITE)
return True
def fake_setuptools():
log.warn('Scanning installed packages')
try:
import pkg_resources
except ImportError:
# we're cool
log.warn('Setuptools or Distribute does not seem to be installed.')
return
ws = pkg_resources.working_set
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
if setuptools_dist is None:
log.warn('No setuptools distribution found')
return
# detecting if it was already faked
setuptools_location = setuptools_dist.location
log.warn('Setuptools installation detected at %s', setuptools_location)
# if --root or --preix was provided, and if
# setuptools is not located in them, we don't patch it
if not _under_prefix(setuptools_location):
log.warn('Not patching, --root or --prefix is installing Distribute'
' in another location')
return
# let's see if its an egg
if not setuptools_location.endswith('.egg'):
log.warn('Non-egg installation')
res = _remove_flat_installation(setuptools_location)
if not res:
return
else:
log.warn('Egg installation')
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
if (os.path.exists(pkg_info) and
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
log.warn('Already patched.')
return
log.warn('Patching...')
# let's create a fake egg replacing setuptools one
res = _patch_egg_dir(setuptools_location)
if not res:
return
log.warn('Patched done.')
_relaunch()
def _relaunch():
log.warn('Relaunching...')
# we have to relaunch the process
args = [sys.executable] + sys.argv
sys.exit(subprocess.call(args))
def _extractall(self, path=".", members=None):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
import copy
import operator
from tarfile import ExtractError
directories = []
if members is None:
members = self
for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0700
self.extract(tarinfo, path)
# Reverse sort directories.
if sys.version_info < (2, 4):
def sorter(dir1, dir2):
return cmp(dir1.name, dir2.name)
directories.sort(sorter)
directories.reverse()
else:
directories.sort(key=operator.attrgetter('name'), reverse=True)
# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError, e:
if self.errorlevel > 1:
raise
else:
self._dbg(1, "tarfile: %s" % e)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
tarball = download_setuptools()
_install(tarball)
if __name__ == '__main__':
main(sys.argv[1:])
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python `which 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 <target>' where <target> 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."
Algorithms
==========
.. automodule:: pymbolic.algorithm
from importlib import metadata
from urllib.request import urlopen
_conf_url = \
"https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py"
with urlopen(_conf_url) as _inf:
exec(compile(_inf.read(), _conf_url, "exec"), globals())
copyright = "2013-24, Andreas Kloeckner"
release = metadata.version("pymbolic")
version = ".".join(release.split(".")[:2])
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
intersphinx_mapping = {
"galgebra": ("https://galgebra.readthedocs.io/en/latest/", None),
"mako": ("https://docs.makotemplates.org/en/latest/", None),
"matchpy": ("https://matchpy.readthedocs.io/en/latest/", None),
"numpy": ("https://numpy.org/doc/stable/", None),
"python": ("https://docs.python.org/3", None),
"sympy": ("https://docs.sympy.org/dev/", None),
"typing_extensions":
("https://typing-extensions.readthedocs.io/en/latest/", None),
"constantdict":
("https://matthiasdiener.github.io/constantdict/", None)
}
autodoc_type_aliases = {
"Expression": "Expression",
"ArithmeticExpression": "ArithmeticExpression",
}
import sys
nitpick_ignore_regex = [
# Avoids this error in pymbolic.typing.
# <unknown>:1: WARNING: py:class reference target not found: ExpressionNode [ref.class] # noqa: E501
# Understandable, because typing can't import primitives, which would be needed
# to resolve the reference.
["py:class", r"ExpressionNode"],
["py:class", r"_Expression"],
["py:class", r"p\.AlgebraicLeaf"],
# Sphinx started complaining about these in 8.2.1(-ish)
# -AK, 2025-02-24
["py:class", r"TypeAliasForwardRef"],
]
sys._BUILDING_SPHINX_DOCS = True
Geometric Algebra
=================
.. automodule:: pymbolic.geometric_algebra
.. vim: sw=4
Welcome to pymbolic!
====================
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.
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
:class:`str`. :mod:`pymbolic` purposefully distinguishes the two.
:mod:`pymbolic` does not perform any 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
.. _custom-manipulation:
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(u)
(x + 1)**5
>>> print(MyMapper()(u))
(x*1)**5
Custom Objects
^^^^^^^^^^^^^^
You can also easily define your own objects to use inside an expression:
.. doctest::
>>> from pymbolic import ExpressionNode, expr_dataclass
>>> from pymbolic.typing import Expression
>>>
>>> @expr_dataclass()
... class FancyOperator(ExpressionNode):
... operand: Expression
...
>>> u
Power(Sum((Variable('x'), 1)), 5)
>>> 17*FancyOperator(u)
Product((17, FancyOperator(Power(Sum((..., 1)), 5))))
As a final example, we can now derive from *MyMapper* to multiply all
*FancyOperator* instances by 2.
.. doctest::
>>> FancyOperator.mapper_method
'map_fancy_operator'
>>> class MyMapper2(MyMapper):
... def map_fancy_operator(self, expr):
... return 2*FancyOperator(self.rec(expr.operand))
...
>>> MyMapper2()(FancyOperator(u))
Product((2, FancyOperator(Power(Product((..., 1)), 5))))
.. automodule:: pymbolic
Pymbolic around the web
-----------------------
* `PyPI package <https://pypi.org/project/pymbolic/>`__
* `Documentation <https://documen.tician.de/pymbolic/>`__
* `Source code (GitHub) <https://github.com/inducer/pymbolic>`__
Contents
--------
.. toctree::
:maxdepth: 2
primitives
mappers
utilities
algorithms
geometric-algebra
misc
🚀 Github <https://github.com/inducer/pymbolic>
💾 Download Releases <https://pypi.org/project/pymbolic>
* :ref:`genindex`
* :ref:`modindex`
.. vim: sw=4
Mappers
=======
.. automodule:: pymbolic.mapper
More specialized mappers
------------------------
Converting to strings and code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: pymbolic.mapper.stringifier
.. automodule:: pymbolic.mapper.c_code
.. automodule:: pymbolic.mapper.graphviz
Some minimal mathematics
^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: pymbolic.mapper.evaluator
.. automodule:: pymbolic.mapper.differentiator
.. automodule:: pymbolic.mapper.distributor
.. automodule:: pymbolic.mapper.collector
.. automodule:: pymbolic.mapper.constant_folder
.. automodule:: pymbolic.mapper.substitutor
Finding expression properties
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: pymbolic.mapper.dependency
.. automodule:: pymbolic.mapper.flop_counter
.. autoclass:: FlopCounter
.. autoclass:: CSEAwareFlopCounter
Analysis tools
^^^^^^^^^^^^^^
.. automodule:: pymbolic.mapper.analysis
Simplification
^^^^^^^^^^^^^^
.. automodule:: pymbolic.mapper.flattener
.. vim: sw=4
Installation
============
This command should install :mod:`pymbolic`::
pip install pymbolic
You may need to run this with :command:`sudo` if you are not in a virtual environment
(not recommended). If you don't already have `pip <https://pypi.org/project/pip>`__,
run this beforehand::
python -m ensurepip
For a more manual installation, download the source, unpack it, and run::
pip install .
This should also install all the required dependencies (see ``pyproject.toml``
for a complete list).
For development, you may want to install in `editable mode
<https://setuptools.pypa.io/en/latest/userguide/development_mode.html>`__::
pip install --no-build-isolation --editable .[test]
Why pymbolic when there's already sympy?
========================================
(This is extracted from an email I (Andreas) sent to Aaron Meurer and Anthony
Scopatz.)
So why not use :mod:`sympy` as an AST for DSLs and code generation? It's a good
question. As you read the points I make below, please bear in mind that I'm not
saying this to 'attack' sympy or to diminish the achievement that it is. Very
much on the contrary--as I said above, sympy does a fantastic job being a
computer algebra. I just don't think it's as much in its element as an IR for
code generation. Personally, I think that's perfectly fine--IMO, the tradeoffs
are different for IRs and efficient computer algebra. In a sense, pymbolic
competes much harder with Python's ast module for being a usable program
representation than with Sympy for being a CAS.
At any rate, to answer your question, here goes:
* First, specifically *because* sympy is smart about its input, and will
rewrite it behind your back. pymbolic is *intended* to be a dumb and
static expression tree, and it will leave its input alone unless you
explicitly tell it not to. In terms of floating point math or around
custom node types that may or may not obey the same rules as scalars,
I feel like 'leave it alone' is a safer default.
* Pickling: https://github.com/sympy/sympy/issues/4297
The very moment code generation starts taking more than a second or
so, you'll want to implement a caching mechanism, likely using Pickle.
* Extensibility of transformation constructs: sympy's built-in traversal
behaviors (e.g. taking derivatives, conversion to string, code
generation) aren't particularly easy to extend. It's important to
understand what I'm talking about here: I would like to be able to
make something that, say, is *like* taking a derivative (or
evaluating, or...), but behaves just a bit differently for a few node
types. This is a need that I've found to be very common in code
generation. In (my understanding of) sympy, these behaviors are
attached to method names, so the only way I could conceivably obtain a
tweaked "diff" would be to temporarily monkeypatch "diff" for my node
type, which is kind of a nonstarter. (unless I'm missing something)
Pymbolic's "mapper" mechanism does somewhat better here--you
simply inherit from the base behavior, implement/override a few
methods, and you're done.
This part is a bit of a red herring though, since this can be
implemented for sympy (and, in fact, `I have
<https://github.com/inducer/pymbolic/blob/main/pymbolic/interop/sympy.py#L47>`__).
Also, I noticed that sympy's codegen module implements something similar (e.g.
`here
<https://github.com/sympy/sympy/blob/master/sympy/printing/fortran.py#L70>`__).
The remaining issue is that most of sympy's behaviors aren't available to
extend in this style.
* Representation of code-like constructs, such as:
* Indexing
* Bit shifts and other bitwise ops:
* Distinguishing floor-div and true-div
* Attribute Access
* I should also mention that pymbolic, aside from maintenance and bug
fixes, is effectively 'finished'. It's pretty tiny, it's not
ambitious, and it's not going to change much going forward. And that
is precisely what I want from a package that provides the core data
structure for something complicated and compiler-ish that I'm building
on top.
User-visible changes
====================
Version 2015.3
--------------
.. note::
This version is currently under development. You can get snapshots from
Pymbolic's `git repository <https://github.com/inducer/pymbolic>`__
* 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.
Glossary
========
.. glossary::
mix-in
See `Wikipedia article <https://en.wikipedia.org/wiki/Mixin>`__.
Be sure to mention the mix-in before the base class being mixed in the
list of base classes. This way, the mix-in can override base class
behavior.