Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
644446e
Cleanup sympde/expr/evaluation.py and expand its docstrings
yguclu Feb 6, 2026
0273634
Cleanup test_topology.py and test_expr.py
yguclu Feb 6, 2026
9783335
Rigorously check arguments of Interface constructor
yguclu Feb 10, 2026
dffa221
Add consistency checks when creating a MappedDomain
yguclu Feb 10, 2026
d31dac7
Make Domain.join() require orientation of interfaces
yguclu Feb 10, 2026
de7eb38
Make Domain export and read the orientation of its interfaces
yguclu Feb 10, 2026
71d2502
Bump version from 0.19.2 to 0.20.0
yguclu Feb 11, 2026
d8eea09
Add assert statements to tests for linear expressions and forms
yguclu Feb 16, 2026
89cbf4b
Add assert statements to tests for bilinear expressions
yguclu Feb 16, 2026
66597b1
Check boundary of 2D and 3D multipatch domains in test_topology.py
yguclu Feb 16, 2026
7326f23
Add TODO item: Edge class should be removed
yguclu Feb 16, 2026
7352539
Use pytest-xdist to run unit tests in parallel
yguclu Feb 16, 2026
9702ef8
Run tests in test_gallery.py serially, because h5py is not thread-safe
yguclu Feb 16, 2026
8b0bcc3
add tests in test_expr
campospinto Sep 2, 2026
4798aab
creating new mapping classes
campospinto Sep 4, 2026
9062936
WP 02a: free the jacobian name in Mapping class
campospinto Sep 4, 2026
23cb8a9
WP 02b — AnalyticMapping as a concrete DefinedMapping
campospinto Sep 4, 2026
ed7c72e
minor fix
campospinto Sep 4, 2026
eb99520
WP 03: structural mappings under StructuralMapping
campospinto Sep 5, 2026
3ca8c54
WP 06a Mapping under SymbolicMapping
campospinto Sep 5, 2026
30ef709
WP 06c: AnalyticMapping self-point-evaluable
campospinto Sep 7, 2026
5b5c0fc
WP 06d-2: delete CallableMapping, fold BasicMapping into SymbolicMapping
campospinto Sep 7, 2026
bd73824
WP 06-4a Sever Mapping from StructuralMapping subclasses
campospinto Sep 8, 2026
b695bef
WP 06d-4a-1: post-review fixes for the structural-mapping sever
campospinto Sep 8, 2026
8405928
WP 06d‑4b — sympde cleanup
campospinto Sep 8, 2026
a48091a
WP 06d-4c-1: SymbolicMapping` gains the constructor; `Mapping` become…
campospinto Sep 8, 2026
2fd3b9a
WP 06d-4c-1b: two defensive fixes from the second `/code-review` round
campospinto Sep 8, 2026
321259b
WP 06d-4c-D1 logical chain rule must sum over every mapping in the ex…
campospinto Sep 9, 2026
a52bb38
WP07: DiscreteMapping: a DefinedMapping backed by an external callable
campospinto Sep 9, 2026
5ae69a9
WP 07b-1 DiscreteMapping survives sympy's func(*args) rebuild
campospinto Sep 10, 2026
b1510a7
WP07d : DiscreteMapping.set_callable_mapping raises error
campospinto Sep 12, 2026
f76d9fb
WP 07e: DiscreteMapping.has_callable_mapping()
campospinto Sep 12, 2026
05f7597
WP 09: guard set_callable_mapping on StructuralMapping
campospinto Sep 14, 2026
f77d83b
improve docstrings for mappings
campospinto Sep 16, 2026
bd8c9e3
WP 12: fix docstring of DefinedMapping
campospinto Sep 17, 2026
6bb2717
WP13: set_callable_mapping now raises an error
campospinto Sep 17, 2026
51228aa
improve set_callable_mapping
campospinto Sep 18, 2026
87e0d4c
WP16: DeprecationWarning for SymbolicMapping.set_callable_mapping
campospinto Sep 22, 2026
d69a82b
D8: AnalyticMapping no longer inherits deprecated Mapping
campospinto Sep 23, 2026
5c2e861
WP19: Analytic Mappings require expressions
campospinto Sep 25, 2026
c2a819d
fixing codacy issues
campospinto Oct 1, 2026
63b5b95
Fix symbolic atom construction for modern SymPy
FrederikSchnack Sep 29, 2026
7cf30eb
Fix integral sum multiplication postprocessing
FrederikSchnack Sep 29, 2026
4ce56d4
Normalize structural arguments for modern SymPy
FrederikSchnack Sep 29, 2026
5e5b670
Support the maintained SymPy release range
FrederikSchnack Sep 29, 2026
70ca60e
Replace obsolete SymPy compatibility helpers
FrederikSchnack Sep 29, 2026
44b8f34
Make symbolic objects safe to reconstruct
FrederikSchnack Sep 29, 2026
0d12ae9
Include function spaces in symbol identity
FrederikSchnack Sep 29, 2026
faf67e6
Document SymPy compatibility upgrade
FrederikSchnack Oct 1, 2026
290a449
Merge branch 'master' into new_mappings_09
campospinto Oct 9, 2026
f6a812c
Merge branch 'upgrade_sympy' into new_mappings_09
campospinto Oct 9, 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
34 changes: 34 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,37 @@ jobs:
working-directory: ./scratch
run: |
coverage report --ignore-errors --show-missing --sort=cover

minimum-sympy:
name: Ubuntu / Python 3.11 / SymPy 1.12
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v6

- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Upgrade pip, setuptools, and wheel
run: |
pip install --upgrade pip setuptools wheel

- name: Install project with minimum SymPy
run: |
pip install .[test] 'sympy == 1.12'
pip freeze

- name: Initialize test directory
run: |
mkdir scratch

- name: Run tests
working-directory: ./scratch
run: >-
pytest -n auto --dist loadgroup
--pyargs sympde -ra
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ 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.
- #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.
- [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

- #140: Fix the rendering of the `Domain.join()` API documentation.
- #166: Restore warning-free documentation builds, including bibliography and mathematical notation support.
- #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.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Mathematics",
]
dependencies = [
'sympy >= 1.5, < 1.10',
'sympy >= 1.12, < 1.15',
'h5py',
'pyyaml',
'numpy',
Expand Down
3 changes: 1 addition & 2 deletions sympde/calculus/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@
from sympy.core.containers import Tuple
from sympy.core.singleton import S
from sympy.core.decorators import call_highest_priority
from sympy.utilities.iterables import is_sequence

from sympde.old_sympy_utilities import is_sequence
from sympde.core.basic import CalculusFunction
from sympde.core.basic import _coeffs_registery
from sympde.topology.space import ScalarFunction, VectorFunction
Expand Down Expand Up @@ -1860,4 +1860,3 @@ def add_basicop(expr):
Basic._constructor_postprocessor_mapping[BasicOperator] = {
"Add": [add_basicop],
}

5 changes: 2 additions & 3 deletions sympde/calculus/matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from sympy import Add, Mul, Pow
from sympy import sympify
from sympy.core.decorators import call_highest_priority
from sympde.core.basic import _coeffs_registery, Basic
from sympde.core.basic import _coeffs_registery, _new_basic, Basic

class MatrixSymbolicExpr(Expr):
is_commutative = False
Expand Down Expand Up @@ -275,7 +275,7 @@ def _sympystr(self, printer):

class MatrixElement(Expr):
def __new__(cls, base, indices, **options):
return Expr.__new__(cls, base, indices)
return _new_basic(cls, base, indices)

@property
def base(self):
Expand All @@ -293,4 +293,3 @@ def _sympystr(self, printer):
"Mul": [lambda x: MatSymbolicMul(*x.args)],
"Add": [lambda x: MatSymbolicAdd(*x.args)]
}

2 changes: 1 addition & 1 deletion sympde/core/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
from sympy import S
from sympy import Basic
from sympy import Indexed, IndexedBase
from sympy.utilities.iterables import is_sequence

from sympde.old_sympy_utilities import is_sequence
from .basic import CalculusFunction
from .basic import _coeffs_registery

Expand Down
78 changes: 70 additions & 8 deletions sympde/core/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,78 @@
from sympy import Function
from sympy import Number
from sympy import NumberSymbol
from sympy import Integer
from sympy import sympify
from sympy.core import Basic
from sympy.core import Symbol
from sympy.tensor import IndexedBase
from sympy.core.containers import Dict, Tuple
from sympy.core.symbol import Str


class _NoneArgument(Basic):
"""SymPy-compatible representation of ``None`` in expression arguments."""


_none = _NoneArgument()


def _sympify_argument(value):
"""Convert structural metadata to objects accepted in ``Basic.args``."""
if isinstance(value, Basic):
return value
if value is None:
return _none
if isinstance(value, str):
return Str(value)
if isinstance(value, dict):
return Dict(*((_sympify_argument(k), _sympify_argument(v))
for k, v in value.items()))
if isinstance(value, (tuple, list)):
return Tuple(*(_sympify_argument(v) for v in value))
return sympify(value)


def _new_basic(cls, *args, **options):
"""Construct a ``Basic`` after normalizing all structural arguments."""
args = tuple(_sympify_argument(arg) for arg in args)
return Basic.__new__(cls, *args, **options)


def _is_none_argument(value):
return value is _none


def _restore_optional_argument(value):
"""Restore an optional value stored in a symbolic argument list."""
return None if _is_none_argument(value) else value


def _restore_string_argument(value):
"""Restore a Python string stored as a SymPy ``Str`` object."""
return value.name if isinstance(value, Str) else value


def _restore_int_argument(value):
"""Restore a Python integer stored as a SymPy ``Integer`` object."""
value = _restore_optional_argument(value)
return int(value) if isinstance(value, Integer) else value


def _restore_container_argument(value):
"""Restore Python containers used as non-expression metadata."""
value = _restore_optional_argument(value)
if isinstance(value, Str):
return value.name
if isinstance(value, Dict):
return {
_restore_container_argument(k): _restore_container_argument(v)
for k, v in value.items()
}
if isinstance(value, Tuple):
return tuple(_restore_container_argument(v) for v in value)
if isinstance(value, Integer):
return int(value)
return value

#==============================================================================
class Constant(Symbol):
Expand Down Expand Up @@ -34,13 +103,6 @@ class CalculusFunction(Function):
functions when manipulating our expressions"""
pass

#==============================================================================
class BasicMapping(IndexedBase):
"""
Represents a basic class for mapping.
"""
pass

#==============================================================================
class BasicDerivable(Basic):
pass
Expand Down
15 changes: 12 additions & 3 deletions sympde/expr/equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from sympde.topology import Trace, trace_0, trace_1
from sympde.calculus import grad, dot
from sympde.core.utils import random_string
from sympde.core.basic import (
_is_none_argument,
_new_basic,
_restore_optional_argument,
)

from .expr import BilinearForm, LinearForm
from .expr import linearize
Expand Down Expand Up @@ -191,6 +196,8 @@ def rhs(self):
class Equation(Basic):

def __new__(cls, lhs, rhs, trials, tests, bc=None, constraint=None):
bc = _restore_optional_argument(bc)
constraint = _restore_optional_argument(constraint)
# ...
if not isinstance(lhs, BilinearForm):
raise UnconsistentLhsError('> lhs must be a bilinear')
Expand Down Expand Up @@ -332,7 +339,7 @@ def __new__(cls, lhs, rhs, trials, tests, bc=None, constraint=None):
tests = Tuple(*tests)
# ...

return Basic.__new__(cls, lhs, rhs, trials, tests, bc, constraint)
return _new_basic(cls, lhs, rhs, trials, tests, bc, constraint)

@property
def lhs(self):
Expand All @@ -352,11 +359,13 @@ def test_functions(self):

@property
def bc(self):
return self._args[4]
bc = self._args[4]
return None if _is_none_argument(bc) else bc

@property
def constraint(self):
return self._args[5]
constraint = self._args[5]
return None if _is_none_argument(constraint) else constraint

#==============================================================================
# TODO must subtitute expr by given args => call then create BasicForm
Expand Down
37 changes: 24 additions & 13 deletions sympde/expr/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from sympde.core.basic import _coeffs_registery
from sympde.core.basic import CalculusFunction
from sympde.core.basic import _new_basic
from sympde.core.algebra import (Dot_1d,
Dot_2d, Inner_2d, Cross_2d,
Dot_3d, Inner_3d, Cross_3d)
Expand Down Expand Up @@ -973,15 +974,15 @@ def _tensorize_atomic_expr(expr, d_atoms):
class Basic1dForm(AtomicExpr):

def __new__(cls, name, axis, weight=S.One):
return Basic.__new__(cls, name, axis, weight)
return _new_basic(cls, name, axis, weight)

@property
def name(self):
return self._args[0]
return self._args[0].name

@property
def axis(self):
return self._args[1]
return int(self._args[1])

@property
def weight(self):
Expand All @@ -1003,38 +1004,48 @@ def _sympystr(self, printer):

class Mass(Basic1dForm):

def __new__(cls, axis, weight=S.One):
def __new__(cls, axis, weight=S.One, *structural_args):
if structural_args:
return Basic1dForm.__new__(cls, axis, weight, *structural_args)
# name = 'Mass'
name = 'M'
return Basic.__new__(cls, name, axis, weight)
return Basic1dForm.__new__(cls, name, axis, weight)

class Stiffness(Basic1dForm):

def __new__(cls, axis, weight=S.One):
def __new__(cls, axis, weight=S.One, *structural_args):
if structural_args:
return Basic1dForm.__new__(cls, axis, weight, *structural_args)
# name = 'Stiffness'
name = 'S'
return Basic.__new__(cls, name, axis, weight)
return Basic1dForm.__new__(cls, name, axis, weight)

class Advection(Basic1dForm):

def __new__(cls, axis, weight=S.One):
def __new__(cls, axis, weight=S.One, *structural_args):
if structural_args:
return Basic1dForm.__new__(cls, axis, weight, *structural_args)
# name = 'Advection'
name = 'A'
return Basic.__new__(cls, name, axis, weight)
return Basic1dForm.__new__(cls, name, axis, weight)

class AdvectionT(Basic1dForm):

def __new__(cls, axis, weight=S.One):
def __new__(cls, axis, weight=S.One, *structural_args):
if structural_args:
return Basic1dForm.__new__(cls, axis, weight, *structural_args)
# name = 'AdvectionT'
name = 'AT'
return Basic.__new__(cls, name, axis, weight)
return Basic1dForm.__new__(cls, name, axis, weight)

class Bilaplacian(Basic1dForm):

def __new__(cls, axis, weight=S.One):
def __new__(cls, axis, weight=S.One, *structural_args):
if structural_args:
return Basic1dForm.__new__(cls, axis, weight, *structural_args)
# name = 'Bilaplacian'
name = 'B'
return Basic.__new__(cls, name, axis, weight)
return Basic1dForm.__new__(cls, name, axis, weight)


Mass_0 = Mass(0)
Expand Down
10 changes: 5 additions & 5 deletions sympde/expr/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from sympy.core import Expr, Add, Mul
from sympy.core.numbers import Zero as sy_Zero
from sympy.core.containers import Tuple
from sympy.utilities.iterables import is_sequence

from sympde.old_sympy_utilities import is_sequence
from sympde.core.basic import CalculusFunction
from sympde.core.basic import Constant
from sympde.core.utils import random_string
Expand Down Expand Up @@ -402,7 +402,7 @@ def __call__(self, *tests, **kwargs):
# Make sure that 'values' is always a list
if len(tests) == 1:
values = tests[0]
if not is_sequence(values, vector=isinstance(values, VectorFunction)):
if isinstance(values, VectorFunction) or not is_sequence(values):
values = [values]
else:
values = tests
Expand Down Expand Up @@ -513,8 +513,8 @@ def __call__(self, trials, tests, **kwargs):
expr = self._update_free_variables(**kwargs)

# If needed, convert positional arguments to lists
if not is_sequence(trials, vector=isinstance(trials, VectorFunction)): trials = [trials]
if not is_sequence(tests, vector=isinstance(tests, VectorFunction)): tests = [tests ]
if isinstance(trials, VectorFunction) or not is_sequence(trials): trials = [trials]
if isinstance(tests, VectorFunction) or not is_sequence(tests): tests = [tests ]

# Concatenate input values into single list
values = [*trials, *tests]
Expand Down Expand Up @@ -855,7 +855,7 @@ def mul_integral(expr):
def mul_add(expr):
args_int = [a for a in expr.args if isinstance(a, IntAdd)]
coeff = Mul(*[a for a in expr.args if not a in args_int])
args_int[0] = [IntAdd(*[coeff*i for i in args_int[0].args])]
args_int[0] = IntAdd(*[coeff*i for i in args_int[0].args])
return Mul(*args_int)

def add_int(expr):
Expand Down
2 changes: 1 addition & 1 deletion sympde/expr/tests/test_equation_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def test_equation_2d_1():
a = BilinearForm((v,u), a1(v,u) + a_B1(v,u))
l = LinearForm(v, l1(v) + l2(v))
equation = Equation(a, l, tests=v, trials=u)
assert equation.func(*equation.args) == equation
# ...

# ...
Expand Down Expand Up @@ -415,4 +416,3 @@ def teardown_module():
def teardown_function():
from sympy.core import cache
cache.clear_cache()

Loading
Loading