Repository navigation
New mapping class hierarchy (sympde part) - #196
Draft
campospinto wants to merge 42 commits into
Draft
campospinto wants to merge 42 commits into
campospinto wants to merge 42 commits into
Conversation
…s a deprecation shell
campospinto
marked this pull request as draft
September 30, 2026 14:54
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the single
Mappingclass with a small hierarchy that keepstwo questions apart: can this mapping be used symbolically? and can it be evaluated at a point?
Today
Mappinganswers both. It is symbolic, and itbecomes point-evaluable only when it has
_expressionsor when a numericalmapping is attached to it after construction with
set_callable_mapping.The new root is
SymbolicMapping. Under it:DefinedMappingis for mappings that are symbolic and point-evaluable,with two concrete classes:
AnalyticMappingfor mappings given by formulas,DiscreteMappingfor mappings backed by a numerical mapping, such as apsydac spline.
StructuralMappingis for mappings that are symbolic only:InverseMapping,InterfaceMappingandMultiPatchMapping.The main user-facing gain: a spline geometry can now have its own symbolic
identity (
DiscreteMapping(F_h, 'F')). You can build a domain from it, joinpatches, and discretize, without mutating another object.
Companion PR: pyccel/psydac#TBD. It uses the new classes and depends on this
PR. sympde itself has no dependency on psydac.
Main changes
New classes (
sympde/topology/mapping.py, exported fromsympde.topology)SymbolicMapping(IndexedBase): the new root.SymbolicMapping('F', dim=2)is an undefined mapping. You can apply it to a domain and use it in forms,
and it carries the symbolic Jacobian and metric. It cannot be evaluated at a
point.
DefinedMapping(SymbolicMapping, BasicCallableMapping): an abstract class formappings that are symbolic and point-evaluable. As a shortcut,
DefinedMapping(callable, name)returns aDiscreteMapping.AnalyticMapping(DefinedMapping): the base class for mappings given byformulas. It turns its own
_expressionsinto NumPy functions the first timethey are needed, and evaluates both scalars and arrays. It is its own
numerical mapping, so
get_callable_mapping()returnsself. All 11 gallerymappings now derive from it (
IdentityMapping,PolarMapping,TargetMapping,CzarnyMapping,TorusMapping, …).DiscreteMapping(DefinedMapping): gives a symbolic name to any externalBasicCallableMapping, such as psydac'sSplineCallableMapping, andforwards every point evaluation to it.
is_analyticalisFalse, soassembly evaluates the geometry on the quadrature grid. The wrapped mapping
is fixed when the object is built and is part of its identity (it counts in
==andhash).has_callable_mapping()is a check that never raises.StructuralMapping(SymbolicMapping): an abstract class for the multipatchrelations
InverseMapping,InterfaceMappingandMultiPatchMapping. Youcan apply them to a domain, but evaluating them at a point raises
TypeError. They also gain a working.nameand.copy().BasicCallableMapping(the plain-Python interface for point evaluation) isunchanged.
Removed or deprecated
CallableMappingis removed, because anAnalyticMappingis its ownnumerical mapping.
sympde.topology.callable_mappingnow only re-exportsBasicCallableMapping.sympde.core.basic.BasicMappingis removed and merged intoSymbolicMapping.Mappingis kept for now, but constructing it directly emits aDeprecationWarning. Legacy subclasses of the formclass X(Mapping)with_expressionskeep working.set_callable_mappingis deprecated onSymbolicMappingand raisesTypeErroronAnalyticMapping,DiscreteMappingandStructuralMapping.Mapping.jacobianis deprecated in favour ofjacobian_symbol.Behaviour changes to watch for downstream
isinstance(x, Mapping)is nowFalsefor analytic and structural mappings.Use
isinstance(x, SymbolicMapping)to mean "any symbolic mapping".AnalyticMapping,F.jacobianis now the numerical methodF.jacobian(*eta). The symbolic Jacobian isF.jacobian_symbol.AnalyticMappingsubclass that has no_expressionsraisesTypeError. UseSymbolicMapping(name, dim=...)for an undefined mappinginstead.
Domain.from_filebuilds its patches fromSymbolicMappingobjects.Interfaceaccepts anySymbolicMapping.Bug fix
sympde/topology/derivatives.py: the chain rule for logical derivatives usedonly one of the mappings in an expression, picked arbitrarily from a set. It
now adds up the contributions of all of them.
Tests
sympde/topology/tests/test_mapping_hierarchy.py(about 1,080 lines)covers the hierarchy, point evaluation, identity and hashing, the guards and
the deprecations. Existing tests are migrated to the new names.
pytest -n auto --dist loadgroup --pyargs sympdegives251 passed, 2 skipped. The examples below run without warnings under
-W error::DeprecationWarning.Documentation
Before: the mapping classes on
master(sympde) anddevel(psydac)classDiagram class IndexedBase { <<sympy>> } class BasicMapping { <<sympde.core, empty>> } class Mapping { name ldim pdim _expressions __call__(domain) MappedDomain jacobian jacobian_expr metric_expr get_callable_mapping() set_callable_mapping(F) } class InverseMapping class InterfaceMapping class MultiPatchMapping class PolarMapping { <<analytic gallery>> } class BasicCallableMapping { <<abc.ABC>> __call__(*eta) jacobian(*eta) jacobian_inv(*eta) metric(*eta) metric_det(*eta) } class CallableMapping { lambdified _expressions } class SplineMapping { <<psydac>> } class NurbsMapping { <<psydac>> } IndexedBase <|-- BasicMapping BasicMapping <|-- Mapping Mapping <|-- InverseMapping Mapping <|-- InterfaceMapping Mapping <|-- MultiPatchMapping Mapping <|-- PolarMapping BasicCallableMapping <|-- CallableMapping BasicCallableMapping <|-- SplineMapping SplineMapping <|-- NurbsMapping Mapping ..> CallableMapping : builds from _expressions Mapping o-- BasicCallableMapping : set_callable_mappingThe problems with this design:
Mappingstands for a formula, a placeholder fora mapping defined elsewhere, and a relation between patches. Nothing in the
type says which one you have.
InverseMapping,InterfaceMappingandMultiPatchMappinginherit a point-evaluation API they cannot honour.Mappingwithout_expressionsbecomes point-evaluable only after something callsset_callable_mappingon it. psydac'sGeometry.read()did this to themappings returned by
Domain.from_file, so whether a mapping could beevaluated depended on which code had run before.
SplineMappingis purelynumerical. To use one in a symbolic domain, it had to be attached to some
other
Mapping.After: the new hierarchy
classDiagram class IndexedBase { <<sympy>> } class SymbolicMapping { name ldim pdim coordinates logical_coordinates __call__(domain) MappedDomain jacobian_symbol jacobian_expr metric_expr is_analytical get_callable_mapping() copy() } class BasicCallableMapping { <<abc.ABC>> __call__(*eta) jacobian(*eta) jacobian_inv(*eta) metric(*eta) metric_det(*eta) ldim pdim } class DefinedMapping { <<abstract>> symbolic + point-evaluable } class AnalyticMapping { _expressions, lambdified on demand } class DiscreteMapping { wraps a BasicCallableMapping has_callable_mapping() } class StructuralMapping { <<abstract>> symbolic only, no point evaluation } class InverseMapping class InterfaceMapping class MultiPatchMapping class PolarMapping { <<analytic gallery>> } class Mapping { <<deprecated>> } class SplineCallableMapping { <<psydac>> from_mapping(F, space=None, ...) to_defined_mapping(name) } class NurbsCallableMapping { <<psydac>> } IndexedBase <|-- SymbolicMapping SymbolicMapping <|-- DefinedMapping BasicCallableMapping <|-- DefinedMapping SymbolicMapping <|-- StructuralMapping SymbolicMapping <|-- Mapping DefinedMapping <|-- AnalyticMapping DefinedMapping <|-- DiscreteMapping AnalyticMapping <|-- PolarMapping StructuralMapping <|-- InverseMapping StructuralMapping <|-- InterfaceMapping StructuralMapping <|-- MultiPatchMapping BasicCallableMapping <|-- SplineCallableMapping SplineCallableMapping <|-- NurbsCallableMapping DiscreteMapping o-- BasicCallableMapping : wrapsThe psydac classes are shown for context; they come in the companion PR
(
SplineMappingis renamedSplineCallableMappingthere, and the old namestill works).
SymbolicMappingAnalyticMapping(+ gallery)_expressions)DiscreteMappingBasicCallableMappingInverseMapping,InterfaceMapping,MultiPatchMappingDomain.joinTypeError)BasicCallableMappingExample
This runs with sympde alone. Outputs are shown as comments.
With psydac (companion PR),
G_his a spline and step 3 readsSplineCallableMapping.from_mapping(P, ncells=..., degree=...).to_defined_mapping('G').discretize(domain)then builds the discrete geometry straight from thesplines, so no geometry file and no
ncellsare needed.Migration
Mapping('F', dim=2)SymbolicMapping('F', dim=2)class M(Mapping): _expressions = {...}class M(AnalyticMapping): _expressions = {...}isinstance(x, Mapping)isinstance(x, SymbolicMapping)F.jacobian(symbolic)F.jacobian_symbolF.get_callable_mapping()on an analytic mappingF;F(*eta),F.jacobian(*eta)can be called directlyCallableMapping(F)Fitself (anAnalyticMapping)M.set_callable_mapping(F_h)DiscreteMapping(F_h, 'M')from sympde.core.basic import BasicMappingfrom sympde.topology import SymbolicMappingMapping,SymbolicMapping.set_callable_mappingand the deprecatedMapping.jacobianare kept, with warnings, so downstream code has time tomigrate. They will be removed in a follow-up PR.