Skip to content

New mapping class hierarchy (psydac part) - #600

Draft
campospinto wants to merge 93 commits into
develfrom
new_mappings_09
Draft

campospinto wants to merge 93 commits into
develfrom
new_mappings_09

Conversation

@campospinto

Copy link
Copy Markdown
Collaborator

Summary

This is the psydac side of the new sympde mapping hierarchy
(pyccel/sympde#196). The class diagram, a worked example and the migration
table are in that PR.

For psydac users, the main change is that a spline geometry is now a
first-class symbolic mapping. A SplineCallableMapping (the former
SplineMapping) can be wrapped as a sympde DiscreteMapping with
F_h.to_defined_mapping(name). Domains built from such mappings are
discretized directly with discretize(domain), in single-patch and multipatch
cases alike, with no geometry file and no ncells. Geometry no longer
modifies the sympde mappings it receives: the set_callable_mapping calls are
gone.

Dependencies

Main changes

Spline mappings (psydac/mapping/discrete.py)

  • SplineMapping is renamed SplineCallableMapping, and NurbsMapping is
    renamed NurbsCallableMapping (see the class diagram in the description of New mapping class hierarchy (sympde part) sympde#196). The old names still import, with a
    DeprecationWarning, and refer to the same classes. The HDF5 type tag is
    unchanged, so existing geometry files are read and written as before.
  • New method to_defined_mapping(name), which returns a sympde
    DiscreteMapping wrapping the spline.
  • The signature of from_mapping changes to
    from_mapping(mapping, space=None, *, ncells=, degree=, periodic=, bounds=, comm=):
    • the spline space is built for you when space is not given;
    • an AnalyticMapping or a DiscreteMapping can be passed directly;
    • the old argument order from_mapping(space, mapping) raises a TypeError
      that names the new call.
  • __call__, jacobian, jacobian_inv, metric and metric_det now accept
    arrays as well as scalars, like AnalyticMapping does. On a tensor grid in a
    serial run, they use a faster evaluation through build_mesh/jac_mat_grid.
    Other inputs fall back to point-by-point evaluation.

Geometry (psydac/cad/geometry.py, psydac/api/discretization.py)

  • New Geometry.from_discrete_domain(domain) for single-patch and multipatch
    domains whose patches are all spline DiscreteMappings. It rebuilds each
    spline on a space that is aware of the interfaces and connects the ghost
    regions, so interface integrals can be assembled. It runs in serial only.
  • discretize(domain) without filename or ncells now uses
    from_discrete_domain. If the domain does not qualify, the error explains
    why, patch by patch.
  • In from_file / read(), the patches of geometry.domain are now spline
    DiscreteMappings. read() no longer calls set_callable_mapping on the
    mappings returned by Domain.from_file.
  • from_discrete_mapping builds a DiscreteMapping patch. It takes an
    optional domain_log=, which is checked against the spline's parametric box.
  • mappings, ncells and periodic are keyed by domain.interior_names in
    every constructor. The old keys (on-disk patch names, integer indices) still
    work, with a DeprecationWarning. The HDF5 output does not change.
  • Fix: read() matched the patches in the file to the domain's interiors by
    position. sympde sorts the interiors by name, so with 11 or more patches
    (patch_10 sorts before patch_2) some patches got the wrong spline.
    Patches are now matched by name.
  • Duplicated geometry-construction code is factored into shared helpers:
    logical_ncube, and in fem/partitioning.py, construct_join_connectivity
    and connectivity_to_join_tuples. This also fixes two bugs in the multipatch
    exporters (cad/multipatch.py): 1-D NURBS export raised TypeError, and it
    now works; dim > 3 raised an IndexError, and it now raises a clear
    NotImplementedError.

Call sites migrated to the new sympde classes

  • Type checks: Mapping becomes SymbolicMapping, and CallableMapping
    becomes AnalyticMapping. This touches api/ast, fem_bilinear_form,
    postprocessing, feec/pushforward and others.
  • The symbolic Jacobian is now read with .jacobian_symbol instead of
    .jacobian.
  • Analytic mappings defined in psydac (Collela3D, and the mappings in tests
    and examples) now derive from AnalyticMapping.
  • PostProcessManager and discretize_space look mappings up by interior name
    directly, without the two-branch fallback.

Examples (examples/notebooks/)

  • poisson_2d_analytic_vs_spline_mapping.ipynb: the same two-patch Poisson
    problem, solved once on analytic mappings and once on spline mappings.
  • feec_timedomain_maxwell_scattering.ipynb: time-domain Maxwell scattering on
    a multipatch domain.
  • feec_potential_hollowtorus.ipynb, with examples/feec/feec_potential_hollowtorus.py:
    harmonic vector potentials in a shaped hollow torus.
  • Old examples updated to the new API.

Tests

  • New tests:
    • mapping/tests/test_spline_callable_mapping_point_eval.py
    • feec/tests/test_spline_mapping_2d.py
    • fem/tests/test_partitioning.py
    • fem/tests/test_integral.py
  • cad/tests/test_geometry.py is much extended: key conventions, export
    round-trips, from_discrete_domain, and the ≥11-patch case.
  • Regression tests for the assembly pipeline, added before the refactor:
    api/tests/test_discretize_dispatch.py, test_grid.py,
    test_mapping_jacobian_pullback.py, test_runtime_assemble.py,
    test_fem_bilinear_form_codegen.py, test_ast_general_path_codegen.py,
    test_ast_parallel_block.py, test_compile_backend_equivalence.py.
  • Locally, with sympde from New mapping class hierarchy (sympde part) sympde#196,
    pytest -n auto --pyargs psydac -m "not mpi and not petsc" gives
    15742 passed, 24 skipped, 15 xfailed.

Usage

import numpy as np
from sympde.topology import Square, Domain, PolarMapping, ScalarFunctionSpace
from psydac.mapping.discrete import SplineCallableMapping
from psydac.api.discretization import discretize

A1 = Square('A1', bounds1=(0., 1.), bounds2=(0., 0.5 * np.pi))
A2 = Square('A2', bounds1=(0., 1.), bounds2=(np.pi, 1.5 * np.pi))
F1 = PolarMapping('F1', dim=2, c1=0.,  c2=0., rmin=0.3, rmax=1.0)
F2 = PolarMapping('F2', dim=2, c1=1.3, c2=0., rmin=1.0, rmax=0.3)

# Spline approximation of an analytic mapping (the space is built for you)
F1_h = SplineCallableMapping.from_mapping(F1, ncells=(8, 8), degree=(2, 2),
                                          bounds=zip(A1.min_coords, A1.max_coords))
F1_h(0.5, 0.7)                     # [0.4971..., 0.4187...]  (scalars or arrays)

# Give it a symbolic identity -> a sympde DiscreteMapping
M1 = F1_h.to_defined_mapping('F1')  # == DiscreteMapping(F1_h, 'F1')
M2 = SplineCallableMapping.from_mapping(F2, ncells=(8, 8), degree=(2, 2),
                                        bounds=zip(A2.min_coords, A2.max_coords)
                                        ).to_defined_mapping('F2')

connectivity = [((0, 1, -1), (1, 1, -1), 1)]
Omega = Domain.join([M1(A1), M2(A2)], connectivity, 'Omega')

# The domain carries its own discrete geometry: no filename, no ncells
Omega_h = discretize(Omega)        # Geometry.from_discrete_domain
V_h     = discretize(ScalarFunctionSpace('V', Omega), Omega_h)  # grid/degree from the splines

To read a geometry file, nothing changes: discretize(domain, filename=f). The
spline of each patch is available as domain_h.mappings[interior_name], and
also as domain_h.domain.mapping.get_callable_mapping().

Breaking changes

  • from_mapping(space, mapping) becomes from_mapping(mapping, space). The old
    order raises a TypeError that names the new call.
  • Domain.from_file(f).mapping.get_callable_mapping() raises ValueError,
    even after discretize(domain, filename=f). Read the spline from the
    geometry instead, as described above. The same applies to building a
    PostProcessManager(domain=Domain.from_file(f)) and a separate
    discretize(domain, filename=f): use PostProcessManager(geometry_file=f).

yguclu and others added 30 commits September 18, 2025 15:27
- Write 3D geometry in parallel without `mpi_dims_mask`
- Read 3D geometry in parallel with `mpi_dims_mask`
- Verify correct distribution of domain for any number of MPI processes
Add parameter `mpi_dims_mask` to class method `from_discrete_mapping` and bound method `read`.
Use new variable for dictionary with number of cells for each patch.
test methods from_discrete_mapping and from_topological_domain with mpi_dims_mask
- Add new constructor Geometry.from_file
- Remove `filename` from __init__ parameters
commit ccbd6e3
Author: Yaman Güçlü <yaman.guclu@gmail.com>
Date:   Wed Oct 1 06:25:42 2025 +0200

    Allow `mpi_dims_mask` with geometry file (#526)

    Add the optional parameter `mpi_dims_mask` to the constructor of class
    `Geometry`, as well as its class methods `from_discrete_mapping` and
    `from_topological_domain`. Add unit tests to verify that the domain is
    correctly decomposed.

    ---------

    Co-authored-by: Alisa Kirkinskaia <alisa.kirkinskaia@tum.de>
    Co-authored-by: Alisa Kirkinskaia <alisa.kirkinsk@gmail.com>
- Add function `get_available_mappings`
- Clean up function `discrete_mapping` and add docstring to it
campospinto and others added 27 commits September 8, 2026 21:45
Tests only, ahead of WP15-1 re-keying Geometry.mappings/ncells/periodic by
interior name. Four pins, all of current behaviour:

- test_geometry_fixture_export_is_byte_identical: from_file -> export
  reproduces geometry.yml/topology.yml byte for byte on the 11 readable
  fixtures, with per-patch points/knots/degree/periodic/weights preserved;
  the 3 plate_with_hole_mp* fixtures as strict xfail(raises=ValueError).
- test_geometry_export_names_in_memory_constructors: the exact geometry.yml
  byte strings for from_discrete_mapping and from_discrete_domain, read ->
  export as a fixed point, and no WP10 double-wrap of interior names.
- test_geometry_mappings_order_matches_interiors: dict order tracks interior
  order on all four construction paths.
- test_geometry_legacy_patch_key_access: today's legacy patch-name keying and
  list-valued periodic on a multipatch file. This is the one pin WP15-1
  deliberately flips.

test_geometry.py 56 passed / 4 xfailed (was 42 / 1), unchanged under
-n 4 --dist loadgroup; with test_build_derham_mapping.py + mapping/tests,
264 passed (was 250). No production code touched; zero sympde edits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 505 complexity · 73 duplication

Metric Results
Complexity 505
Duplication 73

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants