diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..8df2dc3d --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,75 @@ +name: Documentation + +on: + push: + branches: [ master ] + paths: + - 'README.md' + - 'doc/**' + - 'sympde/**.py' + - 'pyproject.toml' + - '.github/workflows/documentation.yml' + + pull_request: + branches: [ master ] + types: + - opened + - reopened + - synchronize + - ready_for_review + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build_docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: | + pyproject.toml + + - name: Upgrade pip, setuptools, and wheel + run: | + pip install --upgrade pip setuptools wheel + + - name: Install project and documentation dependencies + run: | + pip install .[docs] + pip freeze + + - name: Make the Sphinx documentation + run: >- + python -m sphinx -W --keep-going + -b html doc doc/_build/html + + - name: Setup Pages + uses: actions/configure-pages@v6 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + path: 'doc/_build/html' + + deploy_docs: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + needs: build_docs + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/AUTHORS b/AUTHORS index d61f2333..9fbe15ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,7 +8,6 @@ Contributors * Ahmed Ratnani (original author) * Said Hadjout (original author) * Antoine Lavandier -* Martin Campos Pinto * Tom Caruso * Alisa Kirkinskaia * Elena Moral Sánchez diff --git a/CHANGELOG.md b/CHANGELOG.md index c481f87e..89430b40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to this project will be documented in this file. +## Unreleased + +### Added + +- [DEVELOPER] Add a `CHANGELOG.md` file listing changes between different versions. +- [DEVELOPER] Add Python version 3.14 to the testing workflow. +- [DEVELOPER] Add separate documentation dependencies and a strict Sphinx workflow that checks pull requests and publishes the documentation to GitHub Pages. +- [DEVELOPER] Add an `AUTHORS` file listing the project maintainers and contributors. + +### Changed + +- Convert the README to Markdown and expand the installation, testing, and documentation-build instructions. +- [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. + +### Fixed + +- #140: Fix the rendering of the `Domain.join()` API documentation. +- #166: Restore warning-free documentation builds, including bibliography and mathematical notation support. + +### Removed + +- [DEVELOPER] Remove obsolete CI configuration, the standalone pytest configuration, and the legacy test runner script. + +### Deprecated + ## [0.20.0] - 2026-09-07 ### Added diff --git a/README.md b/README.md index 764fae61..52f973c8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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://readthedocs.org/projects/sympde/badge/?version=latest)](http://sympde.readthedocs.io/en/latest/?badge=latest) +[![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. It can be used to provide capabilities similar to the [FEniCS](https://fenicsproject.org/) project by extending and writing your own *printing* functions. @@ -76,11 +76,21 @@ The complete test suite can be run from any directory with: 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: + +```bash +pip install --editable ".[docs]" +sphinx-build -W --keep-going -b html doc doc/_build/html +``` + ## For developers Because many important SymPDE features are only tested in Psydac, new pull requests should also be tested against the Psydac test suite. This can be done by opening a pull request in Psydac whose only change is to install the corresponding SymPDE branch. -To achieve this, modify the line corresponding to `sympde` in Psydac's `pyproject.toml` file. +Because many important SymPDE features are only tested in PSYDAC, new pull requests should also be tested against the PSYDAC test suite. +This can be done by opening a pull request in PSYDAC whose only change is to install the corresponding SymPDE branch. +To achieve this, modify the line corresponding to `sympde` in PSYDAC's `pyproject.toml` file. For instance, to test a new SymPDE branch called `my_feature`, use: diff --git a/doc/conf.py b/doc/conf.py index e8531e46..24786ce0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config +"""Sphinx configuration for the SymPDE documentation.""" + +from pathlib import Path + +from sphinx.ext.apidoc import main as sphinx_apidoc + +from sympde import __version__ # -- Path setup -------------------------------------------------------------- @@ -19,21 +19,21 @@ # -- Project information ----------------------------------------------------- -project = 'sympde' -copyright = '2018, A. Ratnani, S. Hadjout' -author = 'A. Ratnani, S. Hadjout' +project = 'SymPDE' +copyright = '2018-2026, SymPDE developers' +author = 'SymPDE developers' # The short X.Y version -version = '' +version = __version__.split('-dev', maxsplit=1)[0] # The full version, including alpha/beta/rc tags -release = '' +release = version # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # -# needs_sphinx = '1.0' +needs_sphinx = '7.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -42,7 +42,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.imgmath', + 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', 'sphinxcontrib.bibtex', ] @@ -54,7 +54,8 @@ # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = {'.rst': 'restructuredtext'} +default_role = 'py:obj' # The master toctree document. master_doc = 'index' @@ -64,7 +65,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -143,26 +144,13 @@ latex_additional_files = ['latex_macros.sty'] latex_elements = { - 'printmodindex': '', 'printindex': '', 'preamble' : r'\usepackage{amsmath} \usepackage{amssymb} \usepackage{latex_macros}', - 'docclass':'report', } ##################################################### # add LaTeX macros -f = open('latex_macros.sty', 'r') - -try: - imgmath_latex_preamble # check whether this is already defined -except NameError: - imgmath_latex_preamble = "" - -for macro in f: - # used when building html version - imgmath_latex_preamble += macro + '\n' - # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples @@ -192,11 +180,21 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True +bibtex_bibfiles = ['math/refs_feec.bib'] -# -- APIDOC ---------------------------------------------- -import subprocess -cmd = 'rm -rf source; sphinx-apidoc --force --maxdepth=3 -o source/ ../sympde' -subprocess.call(cmd, shell=True) -# create _static directory -subprocess.call('mkdir -p _static', shell=True) +# -- APIDOC ---------------------------------------------- +doc_dir = Path(__file__).resolve().parent +source_dir = doc_dir / 'source' +static_dir = doc_dir / '_static' +source_dir.mkdir(exist_ok=True) +static_dir.mkdir(exist_ok=True) +apidoc_args = [ + '--force', + '--remove-old', + '--maxdepth', '3', + '--output-dir', str(source_dir), + str(doc_dir.parent / 'sympde'), +] +apidoc_args.extend(str(path) for path in (doc_dir.parent / 'sympde').glob('*/tests')) +sphinx_apidoc(apidoc_args) diff --git a/pyproject.toml b/pyproject.toml index ecd04efc..d58959e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,13 @@ test = [ "pytest-cov >= 5", "pytest-xdist >= 3", ] +docs = [ + "sphinx >= 7", + "sphinxcontrib-bibtex >= 2.6", +] [project.urls] +Documentation = "https://pyccel.github.io/sympde/" Issues = "https://github.com/pyccel/sympde/issues" Repository = "https://github.com/pyccel/sympde" diff --git a/sympde/expr/evaluation.py b/sympde/expr/evaluation.py index d3f0ace4..aa148b04 100644 --- a/sympde/expr/evaluation.py +++ b/sympde/expr/evaluation.py @@ -502,8 +502,7 @@ class TerminalExpr(CalculusFunction): Parameters ---------- - expr : Expr | Matrix | ImmutableDenseMatrix (from sympy) - | LogicalExpr (from sympde.topology.mapping) + expr : Expr or Matrix or ImmutableDenseMatrix or LogicalExpr The mathematical expression. domain : BasicDomain (from sympde.topology.basic) diff --git a/sympde/topology/domain.py b/sympde/topology/domain.py index f7cefdea..4fe4ebce 100644 --- a/sympde/topology/domain.py +++ b/sympde/topology/domain.py @@ -434,23 +434,19 @@ def from_file(cls, filename): @classmethod def join(cls, patches, connectivity, name): - """ - Create a multipatch domain by joining two or more patches in 2D or 3D. + """Create a multipatch domain by joining patches in 2D or 3D. Parameters ---------- - patches : list[Domain] - List of patches. - - connectivity : list - List of interfaces, identified by a tuple of 2 boundaries and an orientation - (bound_minus, bound_plus, ornt) where - - Each boundary is identified by a tuple of 3 integers: (patch, axis, ext) - with patches given as objects (or by their indices in the patches list) - and - - In 2D, ornt is an integer that can take the value of 1 or -1 - - In 3D, ornt is a tuple of 3 integers that can take the value of 1 or -1 - (see below for more details) + patches : sequence of Domain + Atomic patches in the joined domain. + + connectivity : sequence of tuple + Interface descriptions of the form `(minus, plus, orientation)`. + Each side is `(patch, axis, ext)`, where `patch` is a patch + object or its index in `patches` and `ext` is `-1` or `1`. + A 2D orientation is `-1` or `1`. A 3D orientation is a tuple + of three values, each equal to `-1` or `1`. name : str Name of the domain. @@ -464,54 +460,56 @@ def join(cls, patches, connectivity, name): ----- The orientations are specified in the same manner as in GeoPDES, see e.g. - and + and T. Dokken, E. Quak, V. Skytt. Requirements from Isogeometric Analysis for changes in product design ontologies, 2010. Example ------- - # list of patches (mapped domains) - Omega_0 = F0(A) - Omega_1 = F1(A) - Omega_2 = F2(A) - Omega_3 = F3(A) - - patches = [Omega_0, Omega_1, Omega_2, Omega_3] - - # integers representing the axes - axis_0 = 0 - axis_1 = 1 - axis_2 = 2 - - # integers representing the extremities: left (-1) or right (+1) - ext_0 = -1 - ext_1 = +1 - - # A connectivity list in 2D - connectivity = [((Omega_0, axis_0, ext_0), (Omega_1, axis_0, ext_1), 1), - ((Omega_1, axis_1, ext_0), (Omega_3, axis_1, ext_1), -1), - ((Omega_0, axis_1, ext_0), (Omega_2, axis_1, ext_1), 1), - ((Omega_2, axis_0, ext_0), (Omega_3, axis_0, ext_1), -1)] - - # alternative option (passing interface patches by their indices in the patches list): - connectivity = [((0, axis_0, ext_0), (1, axis_0, ext_1), 1), - ((1, axis_1, ext_0), (3, axis_1, ext_1), -1), - ((0, axis_1, ext_0), (2, axis_1, ext_1), 1), - ((2, axis_0, ext_0), (3, axis_0, ext_1), -1)] - - # A connectivity list in 3D - connectivity = [((Omega_0, axis_0, ext_1), (Omega_1, axis_0, ext_0), ( 1, 1, 1)), - ((Omega_0, axis_1, ext_1), (Omega_2, axis_1, ext_0), ( 1, -1, 1)), - ((Omega_1, axis_1, ext_1), (Omega_3, axis_1, ext_0), (-1, 1, -1)), - ((Omega_2, axis_0, ext_1), (Omega_3, axis_0, ext_0), (-1, 1, 1))] - - # alternative option (passing interface patches by their indices in the patches list): - connectivity = [((0, axis_0, ext_1), (1, axis_0, ext_0), ( 1, 1, 1)), - ((0, axis_1, ext_1), (2, axis_1, ext_0), ( 1, -1, 1)), - ((1, axis_1, ext_1), (3, axis_1, ext_0), (-1, 1, -1)), - ((2, axis_0, ext_1), (3, axis_0, ext_0), (-1, 1, 1))] - - # the multi-patch domain - Omega = Domain.join(patches=patches, connectivity=connectivity, name='Omega') + .. code-block:: python + + # list of patches (mapped domains) + Omega_0 = F0(A) + Omega_1 = F1(A) + Omega_2 = F2(A) + Omega_3 = F3(A) + + patches = [Omega_0, Omega_1, Omega_2, Omega_3] + + # integers representing the axes + axis_0 = 0 + axis_1 = 1 + axis_2 = 2 + + # integers representing the extremities: left (-1) or right (+1) + ext_0 = -1 + ext_1 = +1 + + # A connectivity list in 2D + connectivity = [((Omega_0, axis_0, ext_0), (Omega_1, axis_0, ext_1), 1), + ((Omega_1, axis_1, ext_0), (Omega_3, axis_1, ext_1), -1), + ((Omega_0, axis_1, ext_0), (Omega_2, axis_1, ext_1), 1), + ((Omega_2, axis_0, ext_0), (Omega_3, axis_0, ext_1), -1)] + + # alternative option (passing interface patches by their indices in the patches list): + connectivity = [((0, axis_0, ext_0), (1, axis_0, ext_1), 1), + ((1, axis_1, ext_0), (3, axis_1, ext_1), -1), + ((0, axis_1, ext_0), (2, axis_1, ext_1), 1), + ((2, axis_0, ext_0), (3, axis_0, ext_1), -1)] + + # A connectivity list in 3D + connectivity = [((Omega_0, axis_0, ext_1), (Omega_1, axis_0, ext_0), ( 1, 1, 1)), + ((Omega_0, axis_1, ext_1), (Omega_2, axis_1, ext_0), ( 1, -1, 1)), + ((Omega_1, axis_1, ext_1), (Omega_3, axis_1, ext_0), (-1, 1, -1)), + ((Omega_2, axis_0, ext_1), (Omega_3, axis_0, ext_0), (-1, 1, 1))] + + # alternative option (passing interface patches by their indices in the patches list): + connectivity = [((0, axis_0, ext_1), (1, axis_0, ext_0), ( 1, 1, 1)), + ((0, axis_1, ext_1), (2, axis_1, ext_0), ( 1, -1, 1)), + ((1, axis_1, ext_1), (3, axis_1, ext_0), (-1, 1, -1)), + ((2, axis_0, ext_1), (3, axis_0, ext_0), (-1, 1, 1))] + + # the multi-patch domain + Omega = Domain.join(patches=patches, connectivity=connectivity, name='Omega') """ assert isinstance(patches, (tuple, list)) assert isinstance(connectivity, (tuple, list)) @@ -1141,5 +1139,3 @@ def split(domain, value): else: raise NotImplementedError('TODO') - - diff --git a/sympde/topology/mapping.py b/sympde/topology/mapping.py index c4844316..585dd85e 100644 --- a/sympde/topology/mapping.py +++ b/sympde/topology/mapping.py @@ -751,13 +751,13 @@ def eval(cls, F): """ this class methods computes the jacobian of a mapping - Parameters: + Parameters ---------- F: Mapping mapping object - Returns: - ---------- + Returns + ------- expr : ImmutableDenseMatrix the jacobian matrix """ @@ -799,7 +799,7 @@ def eval(cls, F, v): """ This class methods computes the covariant transformation - Parameters: + Parameters ---------- F: Mapping mapping object @@ -807,8 +807,8 @@ def eval(cls, F, v): v: the basis function - Returns: - ---------- + Returns + ------- expr : Tuple the covariant transformation """ @@ -848,7 +848,7 @@ def eval(cls, F, v): """ This class methods computes the contravariant transformation - Parameters: + Parameters ---------- F: Mapping mapping object @@ -856,8 +856,8 @@ def eval(cls, F, v): v: the basis function - Returns: - ---------- + Returns + ------- expr : Tuple the contravariant transformation """