Repository navigation
Upgrade SymPy - #193
Upgrade SymPy#193
Conversation
Construct function symbols and differential forms through Symbol so that SymPy initializes their assumptions and free-symbol state. Keep their SymPDE metadata in structural identity without exposing non-Basic values through args. Refs #106
Keep the transformed IntAdd as a SymPy expression instead of wrapping it in a Python list. Modern SymPy strictly sympifies constructor postprocessor results and rejected the list while checking form linearity. Refs #106
Convert string, numeric, container, and optional metadata stored in custom Basic.args to SymPy objects. Restore the public Python values through properties so callers keep the existing API while traversal, printing, hashing, and substitution can safely inspect expression trees. Also preserve integer DifferentialForm dimensions and the historical name-based identity of function symbols. Refs #106
Raise the minimum dependency to SymPy 1.12 for supported Python versions and cap it below 1.15 until the next compatibility review. Keep the regular CI matrix on the newest allowed release and add a Python 3.11 job pinned to the minimum. Refs #106
Use maintained SymPy utilities and native metaclass syntax. The compatibility module remains temporarily available to the legacy package exports until the public API layer removes them.
Accept normalized SymPy arguments in topology, equation, tensor-form, and multipatch-mapping constructors. Preserve structural metadata during rebuilding and cover func/xreplace round trips with regression tests.
Give function spaces structural equality and include them in scalar and vector function hashes. Equal names in different spaces now remain distinct symbols instead of collapsing during SymPy simplification.
2616e37 to
faf67e6
Compare
yguclu
left a comment
There was a problem hiding this comment.
Good job!
Since we have set default_role = 'py:obj' in doc/conf.py, I believe that we can use single backtick marks in all the (new) docstrings.
I have left a few additional comments below
Route _restore_string_argument through _restore_optional_argument like the other restore helpers, so a stored None name is returned as None instead of the internal placeholder.
Replace the remaining _is_none_argument checks with the equivalent restore helpers and inline the placeholder check in _restore_optional_argument.
7873bdf to
f9e1f95
Compare
Co-authored-by: Yaman Güçlü <yaman.guclu@gmail.com>
Drop the leading underscore from new_basic and the restore_*_argument helpers, which are used outside sympde.core.basic. Module-internal helpers stay private.
Replace the custom Integral.__hash__ with the inherited SymPy hash, which already hashes _hashable_content(). It is assigned explicitly because Integral overrides __eq__.
Co-authored-by: Yaman Güçlü <yaman.guclu@gmail.com>
Make the positional parameters of Domain.__new__ follow the order of self.args, so func(*args) reconstructs a domain without a separate code path. dim becomes keyword-only, and connectivity also accepts an iterable of Interface objects. Require interiors or dim up front instead of interiors or connectivity, which failed later on len(None), and update the class and constructor docstrings accordingly. Add a test for rebuilding multipatch domains.
Rebuilding NCube, Line, Square and Cube via func(*args) skipped setting _coordinates, so the rebuilt object fell back to BasicDomain.coordinates, which returns a list and is limited to three dimensions. Route the reconstruction through a shared NCube._rebuild that sets the coordinates from the rebuilt dimension. Document in the constructors that the positional parameters receive the stored Domain arguments when rebuilding, and add a test comparing coordinates before and after func(*args), including a 4D NCube.
The name of a Basic1dForm subclass is fully determined by the class, so store it as a class attribute instead of in args. The constructor signature (axis, weight) now matches args, which removes the structural_args branches from all subclasses. Add a test covering name, axis, weight, printing, func(*args) and that different subclasses stay distinct.
3163eca to
f8683fd
Compare
Purely a formatting change, with no change in behavior. Each "# ..." separator line in the sympde package becomes two empty lines. Blank lines directly after a block opener or at the start or end of a file are dropped.
The Sphinx configuration sets default_role = 'py:obj', so single backticks render as code and link to documented objects. Also mark Domain.__new__ and NCube.__new__ as references.
Follow-up to the separator cleanup, again purely formatting. Inside indented code, the former "# ..." separators now become one empty line instead of two (pycodestyle E303), while module-level gaps keep two. Also remove the remaining separators that were written with a non-breaking space.
The bounds are now part of args, so the custom __hash__ that added them again is redundant. NCubeInterior does not override __eq__, so it can fall back to Basic.__hash__.
Add the user-visible changes made while addressing the review: the new Domain argument order and connectivity input, positional Interface arguments, the new args layout of the 1D forms, the clearer Domain error and the new public helpers in sympde.core.basic.
yguclu
left a comment
There was a problem hiding this comment.
While double-checking the PR description (which I have now updated, please have a look), I noticed that there is no regression test for #198. The existing test_hash only checks that the hashes differ, which was already the case before this PR. Could you add a test like this?
def test_ncube_interior_equality_includes_bounds():
P = Square('P', bounds1=(0, 1), bounds2=(0, 1)).interior
Q = Square('P', bounds1=(0, 2), bounds2=(0, 1)).interior
R = Square('P', bounds1=(0, 1), bounds2=(0, 1)).interior
assert P != Q
assert P == ROptionally, the reproducer from #197 (two mapped multipatch domains differing only in the interface orientation) would also make a good test; at the moment #197 is only covered indirectly by test_multipatch_mapping_reconstruction.
When adding the test(s), please also update the "Validation" section of the PR description (test count and list of new regression coverage).
|
Thanks for catching this! Added both in 8bc7078: |
Adapt the multipatch changes to the conventions introduced by #193 and #194: - Import objects from their defining modules in the tests and in multipatch_gallery.py, since the package initializers are now empty. - Empty sympde/utilities/__init__.py again, and export plot_domain in sympde.api from sympde.utilities.plotting. - Restore the version of sympde/utilities/utils.py from this branch, which only contains lambdify_sympde: the merge had brought back the old plotting functions. - Allow passing mapping and logical_domain of an Interface positionally, so that it can be reconstructed from its args. - Use orientation instead of ornt in test_structural_arguments_are_sympy_objects.
Summary
Update SymPDE to support the maintained SymPy release range (fixes #106):
To this end, address compatibility problems caused by stricter symbolic construction, traversal, reconstruction, hashing, and sympification behavior in recent SymPy versions. Along the way, fix two equality bugs in the topology module (fixes #197, fixes #198).
Compatibility issues and fixes
1. Incorrect construction of custom
SymbolsubclassesScalarFunction,VectorFunction, andDifferentialFormwere constructed throughExpr.__new__orBasic.__new__instead of SymPy'sSymbolconstructor.Recent SymPy versions expect symbol subclasses to initialize Symbol-specific state, including assumptions, commutativity, and free-symbol behavior. Without this initialization:
free_symbolscould be incorrect;The affected objects are now constructed through
Symbol.__xnew__.SymPDE-specific metadata, such as the function space or differential-form degree and dimension, is included through
_hashable_contentrather than being passed as invalid raw symbol arguments.Regression tests verify:
free_symbols;2. Non-SymPy values stored in
Basic.argsSeveral SymPDE expression classes stored ordinary Python objects directly in their structural arguments, including:
None;Modern SymPy traverses, hashes, prints, sympifies, and reconstructs every object exposed through
Basic.args. Raw Python metadata could therefore cause failures during operations such as:A common structural-argument normalization layer was introduced in
sympde.core.basic. The public helpernew_basic()constructs aBasicobject after normalizing its arguments:Strobjects;Integer;Tupleobjects;Dictobjects;Nonevalues use a private SymPy-compatible sentinel.The matching public helpers
restore_optional_argument(),restore_string_argument(),restore_int_argument(), andrestore_container_argument()convert these values back to their historical Python representation when accessed through public properties. This preserves the existing SymPDE API while keeping expression trees valid for SymPy.The normalization is applied to objects including domains, boundaries, interfaces, equations, matrix elements, traces, one-dimensional forms, and multipatch mappings.
3. Expressions could not be reconstructed from
funcandargsSymPy assumes that symbolic expressions can generally be rebuilt using:
This mechanism is used internally by substitution,
xreplace, simplification, and other tree transformations.A number of SymPDE constructors did not satisfy this contract because:
args;Constructors were updated to accept and restore normalized structural arguments. Reconstruction now retains information such as:
NCube,Line,Square, andCube;Where possible, the constructor signatures now follow the order of
args, so that no separate reconstruction code path is needed:Domain(name, interiors, boundaries, mapping, logical_domain, connectivity, *, dim): the parameters may now be passed positionally,dimis keyword-only, andconnectivityalso accepts an iterable ofInterfaceobjects (which is what is stored inargs).Interface(name, bnd_minus, bnd_plus, ornt, mapping, logical_domain):ornt,mapping, andlogical_domainmay now be passed positionally, in this order.Mass,Stiffness,Advection,AdvectionT,Bilaplacian) store onlyaxisandweightin theirargs; their name is now a class attribute.NCube,Line,Square, andCubekeep their user-facing signatures. When SymPy rebuilds them, the storedDomainarguments are forwarded toDomain.__new__, and the coordinates are recreated. Their docstrings describe this behavior.Regression tests now exercise both reconstruction and substitution:
4. Multipatch mappings used raw dictionaries as symbolic arguments
MultiPatchMappingpreviously placed a Python dictionary directly inBasic.argsand implemented a custom__hash__. Moreover, it inherited_hashable_contentfromMapping, which accesses attributes thatMultiPatchMappingdoes not have. As a result, comparing two multipatch domains with==raised anAttributeError(fixes #197).Recent SymPy operations expect structural arguments themselves to be symbolic and reconstructible. This caused problems during hashing, replacement, and rebuilding.
The mapping dictionary is now stored structurally as a SymPy
Dict, while the publicmappingsproperty continues to return a regular Python dictionary. Equality and hashing are derived from the normalized structural arguments instead of a separate custom implementation.Tests verify that multipatch mappings survive both reconstruction and
xreplace.5. Inconsistent equality and hashing
Several classes defined a custom
__hash__that did not match their equality:u_from_V + u_from_Wcould be simplified as though both functions were the same symbol.NCubeInteriorobjects with the same name but different parametric bounds compared equal, but had different hashes (fixesNCubeInteriorequality ignores parametric bounds, and is inconsistent with its hash #198).Function spaces now define structural equality from their name, domain, shape, and kind. Scalar and vector function symbols include their function space in
_hashable_content.NCubeInteriorstoresmin_coordsandmax_coordsin itsargs.The custom
__hash__methods ofBasicFunctionSpace,ScalarFunction,VectorFunction,NCubeInterior, andMultiPatchMappingwere removed in favor of SymPy'sBasic.__hash__, which is computed from_hashable_contentand cached.Integralnow reusesCalculusFunction.__hash__.This ensures that:
6. Constructor postprocessing returned a Python list
The integral multiplication postprocessor wrapped a transformed
IntAddexpression in a Python list before passing it back toMul.Recent SymPy versions strictly sympify constructor-postprocessor results and reject this list during form-linearity checks.
The postprocessor now returns the transformed
IntAddexpression directly:IntAdd(...)instead of:
[IntAdd(...)]7. Legacy SymPy compatibility helpers were outdated
SymPDE carried local copies of compatibility utilities for:
is_sequence;These helpers were replaced with maintained functionality:
sympy.utilities.iterables.is_sequence;VectorFunctionhandling remains explicit where its indexed behavior differs from ordinary Python sequences.The compatibility module itself remains temporarily available because it is still exported by the legacy package namespace. It is removed by the subsequent public-API PR.
8. Domain subclasses were not printed correctly in LaTeX
The custom LaTeX printer only implemented
_print_Domain. Some reconstructed domain-related objects are subclasses ofBasicDomainrather thanDomain, causing modern SymPy's printer dispatch to miss the custom representation.A
_print_BasicDomainimplementation was added so domain interiors and interfaces consistently render using their symbolic names.Regression tests cover multipatch interiors and interfaces.
9. Creating a
Domainfrom a connectivity onlyThe documentation of
Domainstated that a domain can be created from its name and connectivity only, but doing so failed withTypeError: object of type 'NoneType' has no len().Domainnow raises aValueErrorwhen neitherinteriorsnordimis given, and the docstrings of the class and of its constructor were corrected accordingly.Code cleanup
In the modified modules and their tests, the
# ...separator comments were replaced by blank lines, and commented-out code was removed.Dependency and CI changes
1.5to1.12.1.15.3.11job pinned to SymPy1.12.The upper bound remains intentionally below SymPy
1.15until that release range receives a separate compatibility review.Validation
The test suite passes with the upgraded symbolic implementation (SymPy 1.14.0):
New regression coverage includes:
free_symbolsbehavior;NCubeInteriorobjects with different parametric bounds (NCubeInteriorequality ignores parametric bounds, and is inconsistent with its hash #198);func(*args)reconstruction, including multipatch domains, one-dimensional forms, and the coordinates ofNCubeand its subclasses;xreplacesubstitutions;