Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eedbfbf
Add an explicit public API
FrederikSchnack Oct 5, 2026
9ec5073
Adopt explicit internal imports
FrederikSchnack Oct 5, 2026
7c22e57
Replace legacy tutorials with executable examples
FrederikSchnack Oct 5, 2026
cdece53
Document the public API and examples
FrederikSchnack Oct 5, 2026
d418e0f
Document public API changes
FrederikSchnack Oct 5, 2026
0d0b1fe
Import from defining modules in the unit tests
FrederikSchnack Oct 19, 2026
6e83591
Keep __version__ in the top-level package
FrederikSchnack Oct 19, 2026
d55c546
Clarify the wording in the quickstart guide
FrederikSchnack Oct 19, 2026
1e85500
State the complex-square mapping formula explicitly
FrederikSchnack Oct 19, 2026
76b8bc6
Inline the integration helper in the nonlinear example
FrederikSchnack Oct 19, 2026
a0b8d1e
Clarify the plot test for unmapped domains
FrederikSchnack Oct 19, 2026
67ff19a
Say "circular dependency" in the lazy-import comment
FrederikSchnack Oct 19, 2026
306674a
Reference PR #194 in the changelog
FrederikSchnack Oct 19, 2026
2c2aa9d
Restrict sympde.api to the objects used downstream
FrederikSchnack Oct 19, 2026
5807f07
Remove unused expression-tree images
FrederikSchnack Oct 19, 2026
65271a0
Rename the "Nonlinear linearization" example heading
FrederikSchnack Oct 19, 2026
d27b7de
Draw expression trees in the documentation
FrederikSchnack Oct 19, 2026
23f35db
Remove a duplicate import in test_logical_expr
FrederikSchnack Oct 19, 2026
13f9ce1
Use sympde.api in the calculus docstring examples
FrederikSchnack Oct 19, 2026
38c3b10
Use defining-module paths in the plotting docstrings
FrederikSchnack Oct 19, 2026
74c5b2b
Record the removal of the notebooks in the changelog
FrederikSchnack Oct 19, 2026
d2ec86d
Make module-level names used outside their module public
FrederikSchnack Oct 20, 2026
9db4bd0
Update CHANGELOG.md
FrederikSchnack Oct 20, 2026
3b2a584
Update CHANGELOG.md
FrederikSchnack Oct 20, 2026
3ce21d0
Update CHANGELOG.md
FrederikSchnack Oct 20, 2026
0d3efd0
Update CHANGELOG.md
FrederikSchnack Oct 20, 2026
309b06e
Draw the expression trees without writing files
FrederikSchnack Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,22 @@ jobs:
run: |
pip install --upgrade pip setuptools wheel

- name: Install Graphviz
run: |
sudo apt-get update
sudo apt-get install --yes graphviz

- name: Install project and documentation dependencies
run: |
pip install .[docs]
pip freeze

- name: Run documentation examples
run: |
for example in doc/examples/*.py; do
python "$example"
done

- name: Make the Sphinx documentation
run: >-
python -m sphinx -W --keep-going
Expand Down
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file.

### Added

- #194: Add `sympde.api` as the supported flat public API. It contains the objects used by PSYDAC, IGA-Python, GeLaTo and the SymPDE documentation.
- #194: Add example scripts to the documentation, which are run by the documentation workflow.
- #194: Draw expression trees in the documentation with Graphviz, generated from the current code at build time.
- #106: Accept the connectivity of a `Domain` as an iterable of `Interface` objects, in addition to a `Connectivity` object.
- [DEVELOPER] Add a `CHANGELOG.md` file listing changes between different versions.
- [DEVELOPER] Add Python version 3.14 to the testing workflow.
Expand All @@ -15,14 +18,20 @@ All notable changes to this project will be documented in this file.

### Changed

- Convert the README to Markdown and expand the installation, testing, and documentation-build instructions.
- #190: Convert the README to Markdown and expand the installation, testing, and documentation-build instructions.
- #194: Use explicit internal imports and load optional dependencies only when needed.
- #194: Remove the leading underscore from `partial_derivatives`, `logical_partial_derivatives` (`sympde.topology.derivatives`) and `split_test_function` (`sympde.expr.evaluation`), and add them to `sympde.api`.
- #194: List only public names in the `__all__` of every module.
- #106: Require SymPy 1.12 or newer and support maintained SymPy releases below 1.15.
- #106: Update SymPDE's symbolic objects for modern SymPy's structural arguments, traversal, substitution, and reconstruction requirements.
- #106: Order the parameters of `Domain` like its `args` (`name`, `interiors`, `boundaries`, `mapping`, `logical_domain`, `connectivity`) and allow passing them positionally. `dim` is now keyword-only.
- #106: Allow passing `ornt`, `mapping` and `logical_domain` of an `Interface` positionally, in this order.
- #106: Store only `axis` and `weight` in the `args` of the 1D forms (`Mass`, `Stiffness`, `Advection`, `AdvectionT`, `Bilaplacian`). Their name is now a class attribute.
- [DEVELOPER] Modernize the package metadata and consolidate the pytest and coverage configuration in `pyproject.toml`.
- [DEVELOPER] Modernize the Sphinx configuration, API-documentation generation, and API docstring markup.
- [DEVELOPER] Remove the leading underscore from the module-level names used by other SymPDE modules, and rename `_coeffs_registery` to `coefficient_types`.
- [DEVELOPER] Modernize the package metadata and consolidate the pytest and coverage configuration in `pyproject.toml`.
- [DEVELOPER] Modernize the Sphinx configuration, API-documentation generation, and API docstring markup.
- [DEVELOPER] Test the minimum supported SymPy version in a dedicated CI job.

### Fixed
Expand All @@ -32,10 +41,20 @@ All notable changes to this project will be documented in this file.
- #106: Preserve symbol assumptions and free-symbol information with modern SymPy.
- #106: Keep same-named functions from different function spaces distinct during symbolic simplification.
- #106: Fix form-linearity checks involving integral sums with modern SymPy.
- #194: Fix plotting of unmapped two-dimensional domains with `plot_2d()`.
- #106: Preserve symbol assumptions and free-symbol information with modern SymPy.
- #106: Keep same-named functions from different function spaces distinct during symbolic simplification.
- #106: Fix form-linearity checks involving integral sums with modern SymPy.
- #106: Raise a `ValueError` when a `Domain` is created without `interiors` or `dim`, instead of failing with a `TypeError` when only a connectivity is given.
- #191: Fix the rendering of the `Domain.join()` API documentation.
- #166: Restore warning-free documentation builds, including bibliography and mathematical notation support.

### Removed

- #194: Remove the re-exports of public objects from the package and subpackage initializers. Replace imports such as `from sympde.topology import Square` with `from sympde.api import Square`. `from sympde import __version__` keeps working.
- #194: Remove the `sympde.old_sympy_utilities` module. Use `sympy.utilities.iterables.is_sequence` and Python's `metaclass` keyword instead of its `is_sequence` and `with_metaclass`.
- #194: Remove the outdated Jupyter notebooks and the Binder badge. The example scripts in the documentation replace them.
- #194: Remove the unused `_is_op_test_function` from `sympde.calculus.core`.
- [DEVELOPER] Remove obsolete CI configuration, the standalone pytest configuration, and the legacy test runner script.

### Deprecated
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# SymPDE

[![CI status](https://github.com/pyccel/sympde/actions/workflows/testing.yml/badge.svg?branch=master&event=push)](https://github.com/pyccel/sympde/actions/workflows/testing.yml)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyccel/sympde/master)
[![Documentation Status](https://github.com/pyccel/sympde/actions/workflows/documentation.yml/badge.svg)](https://github.com/pyccel/sympde/actions/workflows/documentation.yml)

**SymPDE** is a symbolic calculus library for partial differential equations and variational forms.
Expand Down Expand Up @@ -68,6 +67,17 @@ To check out a specific branch, tag, or commit named `<TAG>`, run `git checkout
pip install --editable ".[test]"
```

### Public API

User-facing objects can be imported from the flat, explicit API without
knowing which internal module defines them:

```python
from sympde.api import Cube, Mapping, Constant
from sympde.api import ScalarFunctionSpace, VectorFunctionSpace, elements_of
from sympde.api import BilinearForm, LinearForm, Norm
```

### Running the tests

The complete test suite can be run from any directory with:
Expand All @@ -77,7 +87,9 @@ pytest -n auto --dist loadgroup --pyargs sympde -ra
```

The documentation dependencies are installed separately, and the HTML pages
are built with warnings treated as errors:
are built with warnings treated as errors. Drawing the expression trees also
requires [Graphviz](https://graphviz.org) (for example `sudo apt install
graphviz` on Ubuntu):

```bash
pip install --editable ".[docs]"
Expand Down
49 changes: 22 additions & 27 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
Advanced expression manipulation
********************************

In this section, we shall present different different features for manipulating expressions such us finding the Kronecker representation for bilinear forms.
Tensor-product representation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Tensorization
^^^^^^^^^^^^^
``TensorExpr`` lowers an integrated form and expresses it through one-
dimensional symbolic factors. This is useful to inspect the Kronecker
structure consumed by tensor-product discretizations.

Assume we have the following bilinear form for Laplace
.. literalinclude:: examples/tensorization.py
:language: python

.. code-block:: python
The example is a complete script, which the documentation workflow runs
before Sphinx builds the page.

from sympde import grad, dot
from sympde import FunctionSpace
from sympde import TestFunction
from sympde import BilinearForm
from sympde.core import tensorize
Logical expressions
^^^^^^^^^^^^^^^^^^^

V = FunctionSpace('V', ldim=2)
U = FunctionSpace('U', ldim=2)
``LogicalExpr`` pulls expressions on a mapped physical domain back to its
logical domain. The analytical-mapping example demonstrates symbolic and
numerical use of this transformation:

v = TestFunction(V, name='v')
u = TestFunction(U, name='u')
.. literalinclude:: examples/analytical_mappings.py
:language: python

a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u)
print(tensorize(a))
Nonlinear forms
^^^^^^^^^^^^^^^

the result is then
``linearize`` differentiates a nonlinear residual with respect to a field and
returns its symbolic Jacobian form:

.. code-block:: python

>>> Mass(v1,u1)xMass(v0,u0) + Mass(v1,u1)xStiffness(v0,u0) + Stiffness(v1,u1)xMass(v0,u0)

which is what we expect, when splitting the integrals with respect to the two directions.
Printing the corresponding latex code gives

.. math::

{\int_{0}^{1} u_{1} v_{1} dy}\otimes {\int_{0}^{1} u_{0} v_{0} dx} + {\int_{0}^{1} u_{1} v_{1} dy}\otimes {\int_{0}^{1} u_{0}^\prime v_{0}^\prime dx} + {\int_{0}^{1} u_{1}^\prime v_{1}^\prime dy}\otimes {\int_{0}^{1} u_{0} v_{0} dx}
.. literalinclude:: examples/nonlinear_poisson.py
:language: python
19 changes: 19 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Public API
==========

The :mod:`sympde.api` package provides the supported flat namespace for
user-facing objects. For example:

.. code-block:: python

from sympde.api import Cube, Mapping, Constant
from sympde.api import ScalarFunctionSpace, elements_of
from sympde.api import BilinearForm, LinearForm, Norm

The complete namespace is listed below. These objects are also documented and
indexed at their defining modules, so this page does not create duplicate
cross-reference targets.

.. automodule:: sympde.api
:members:
:no-index:
29 changes: 29 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Sphinx configuration for the SymPDE documentation."""

import runpy
from pathlib import Path

from docutils.statemachine import StringList
from sphinx.ext.apidoc import main as sphinx_apidoc
from sphinx.ext.graphviz import Graphviz

from sympde import __version__

Expand Down Expand Up @@ -44,6 +47,7 @@
'sphinx.ext.todo',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
'sphinx.ext.graphviz',
'sphinxcontrib.bibtex',
]

Expand Down Expand Up @@ -182,6 +186,9 @@

bibtex_bibfiles = ['math/refs_feec.bib']

# Draw graphs as SVG, so that they scale with the page
graphviz_output_format = 'svg'


# -- APIDOC ----------------------------------------------
doc_dir = Path(__file__).resolve().parent
Expand All @@ -197,4 +204,26 @@
str(doc_dir.parent / 'sympde'),
]
apidoc_args.extend(str(path) for path in (doc_dir.parent / 'sympde').glob('*/tests'))
apidoc_args.append(str(doc_dir.parent / 'sympde' / 'api'))
sphinx_apidoc(apidoc_args)


# -- Expression trees ------------------------------------
# Run the example and draw its graphs with Graphviz. They are generated at
# build time, so they always match the code.
trees = runpy.run_path(str(doc_dir / 'examples' / 'expression_trees.py'))


class ExpressionTree(Graphviz):
"""Draw a graph of the expression-tree example, given by its name."""
required_arguments = 1
optional_arguments = 0

def run(self):
graph = trees['graphs'][self.arguments.pop()]
self.content = StringList(graph.splitlines())
return super().run()


def setup(app):
app.add_directive('expression-tree', ExpressionTree)
47 changes: 47 additions & 0 deletions doc/examples/analytical_mappings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Work with built-in and user-defined analytical mappings."""

import numpy as np
from sympy import pi, sin, symbols

from sympde.api import CollelaMapping2D, Constant, Domain, LogicalExpr, Mapping


# Inspect a built-in mapping symbolically.
mapping = CollelaMapping2D('M', dim=2)
mapped_domain = mapping(Domain('Omega', dim=2))
x1, x2 = symbols('x1, x2', real=True)
eps, k1, k2 = [Constant(name) for name in ('eps', 'k1', 'k2')]

expected_x = 2.0 * (
x1 + eps * sin(2.0 * pi * k1 * x1) * sin(2.0 * pi * k2 * x2)
) - 1.0
assert LogicalExpr(mapping[0], mapped_domain) == expected_x


# Fix its parameters and evaluate it numerically.
numerical_mapping = CollelaMapping2D(
'M_numeric',
dim=2,
eps=0.1,
k1=1.0,
k2=1.0,
).get_callable_mapping()
np.testing.assert_allclose(numerical_mapping(0.25, 0.5), (-0.5, 0.0))


# Define a custom mapping by specifying its coordinate expressions.
class ComplexSquareMapping(Mapping):
"""
Map (x1, x2) to (x, y) such that x + i*y = A * (x1 + i*x2)**2.

Expanding the square gives x = A * (x1**2 - x2**2) and y = 2 * A * x1 * x2.
"""

_expressions = {
'x': 'A * (x1**2 - x2**2)',
'y': '2 * A * x1 * x2',
}
Comment thread
yguclu marked this conversation as resolved.
Resolved


custom_mapping = ComplexSquareMapping('F', dim=2, A=1.0).get_callable_mapping()
np.testing.assert_allclose(custom_mapping(0.5, 0.25), (0.1875, 0.25))
40 changes: 40 additions & 0 deletions doc/examples/differential_calculus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Verify representative symbolic vector-calculus identities."""

from sympy import expand

from sympde.api import Constant, Domain, ScalarFunctionSpace, VectorFunctionSpace
from sympde.api import curl, div, elements_of, grad, laplace


domain = Domain('Omega', dim=3)
scalar_space = ScalarFunctionSpace('V', domain)
vector_space = VectorFunctionSpace('W', domain)

alpha, beta = [Constant(name) for name in ('alpha', 'beta')]
f, g, h = elements_of(scalar_space, names='f, g, h')
field_f, field_g, field_h = elements_of(vector_space, names='F, G, H')

# Gradient linearity and product rules.
assert grad(f + g) == grad(f) + grad(g)
assert grad(alpha * f + beta * g) == alpha * grad(f) + beta * grad(g)
assert grad(f * g) == f * grad(g) + g * grad(f)
assert grad(f / g) == -f * grad(g) / g**2 + grad(f) / g
assert expand(grad(f * g * h)) == (
f * g * grad(h) + f * h * grad(g) + g * h * grad(f)
)

# Linearity of curl, Laplace, and divergence operators.
assert curl(field_f + field_g) == curl(field_f) + curl(field_g)
assert curl(alpha * field_f + beta * field_g) == (
alpha * curl(field_f) + beta * curl(field_g)
)
assert laplace(f + g) == laplace(f) + laplace(g)
assert laplace(alpha * f + beta * g) == alpha * laplace(f) + beta * laplace(g)
assert div(field_f + field_g) == div(field_f) + div(field_g)
assert div(alpha * field_f + beta * field_g) == (
alpha * div(field_f) + beta * div(field_g)
)

# Exact-sequence identities.
assert curl(grad(h)) == 0
assert div(curl(field_h)) == 0
34 changes: 34 additions & 0 deletions doc/examples/expression_trees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Write the expression trees of symbolic expressions in the DOT language."""

from sympy.printing.dot import dotprint

from sympde.api import Domain, ScalarFunctionSpace, div, dx, dy, elements_of, grad


domain = Domain('Omega', dim=2)
space = ScalarFunctionSpace('V', domain)
u, v = elements_of(space, names='u, v')


# [operator-start]
def L(w):
return -div(grad(w)) + w


operator_graph = dotprint(L(u))
# [operator-end]


# [derivative-start]
expr = dx(dy(u * v))
assert expr == u * dx(dy(v)) + v * dx(dy(u)) + dx(u) * dy(v) + dx(v) * dy(u)

derivative_graph = dotprint(expr)
# [derivative-end]


# Graphs drawn in the documentation, by file name
graphs = {
'operator': operator_graph,
'derivative': derivative_graph,
}
Loading
Loading