tensormesh.distributed.assembler 源代码
"""``@distributed`` class decorator for Assembler classes.
Turns any single-device ``ElementAssembler`` / ``NodeAssembler``
subclass into its distributed counterpart by overriding two methods:
* ``from_mesh(dmesh)`` -- accepts a :class:`DistributedMesh` instead
of a single :class:`Mesh`;
* ``__call__()`` -- runs per-rank submesh assembly via the
existing :func:`distributed_element_assemble_per_rank` /
:func:`distributed_node_assemble` infrastructure and wraps the
result in :class:`~tensormesh.sparse.DSparseMatrix` (element) or
returns a per-rank tensor (node).
Usage::
from tensormesh.assemble import LaplaceElementAssembler
from tensormesh.distributed import distributed, DistributedMesh
DistLaplace = distributed(LaplaceElementAssembler)
K_dist = DistLaplace.from_mesh(dmesh)() # -> DSparseMatrix
This avoids the alternative of defining a parallel ``DAssembler`` class
hierarchy alongside every existing ``Assembler`` -- one decorator covers
the whole set, including third-party assemblers the user writes.
"""
from __future__ import annotations
from typing import Optional, Type, TypeVar
T = TypeVar("T")
[文档]
def distributed(asm_cls: Type[T]) -> Type[T]:
"""Class decorator: turn an Assembler class into a distributed one.
The wrapped class shares the original's weak form, quadrature setup
and element kernel -- only the entry points (``from_mesh`` +
``__call__``) are swapped to run a distributed assembly path.
Parameters
----------
asm_cls
An ``ElementAssembler`` or ``NodeAssembler`` subclass. The
decision between matrix- and vector-flavoured assembly is taken
at the wrapped ``__call__`` based on the original class type.
Returns
-------
A new subclass of ``asm_cls`` with overridden entry points; the
name is prefixed with ``Distributed`` for repr / debugging clarity.
"""
# Import lazily to avoid a circular import with the assembly module
# (which itself imports from tensormesh.assemble for the base classes).
from .assemble import (
distributed_element_assemble,
distributed_node_assemble,
)
# Element vs Node detection: inspect MRO names to avoid heavy imports.
is_element = any(
c.__name__ == "ElementAssembler" for c in asm_cls.__mro__
)
is_node = any(
c.__name__ == "NodeAssembler" for c in asm_cls.__mro__
)
if not (is_element or is_node):
raise TypeError(
f"{asm_cls.__name__} is neither an ElementAssembler nor a "
"NodeAssembler subclass; @distributed cannot wrap it."
)
class _DistributedWrapped(asm_cls): # type: ignore[misc, valid-type]
# The wrapper stores enough state to defer assembly until
# ``__call__``; the parent __init__ is bypassed because we don't
# have a Mesh, only a DistributedMesh.
@classmethod
def from_mesh(cls, dmesh, **kw): # type: ignore[override]
"""Build a distributed assembler bound to ``dmesh``.
Mirrors :meth:`ElementAssembler.from_mesh` but accepts a
:class:`DistributedMesh`. The actual assembly is deferred to
:meth:`__call__`.
"""
self = cls.__new__(cls)
self._dmesh = dmesh
self._from_mesh_kw = kw
return self
def __call__(self, **call_kw): # type: ignore[override]
"""Trigger the distributed assembly.
Returns
-------
:class:`~tensormesh.sparse.DSparseMatrix` (element flavour)
or a per-rank :class:`torch.Tensor` (node flavour).
"""
if is_element:
from . import DSparseMatrix
dst = distributed_element_assemble(
asm_cls, self._dmesh,
**self._from_mesh_kw,
call_kwargs=call_kw or None,
)
# dst is a DSparseTensor; wrap with a fresh UUID since
# this is the first DSparseMatrix derived from this
# partition build.
return DSparseMatrix(dst)
# Node assembler
return distributed_node_assemble(
asm_cls, self._dmesh,
**self._from_mesh_kw,
call_kwargs=call_kw or None,
)
_DistributedWrapped.__name__ = f"Distributed{asm_cls.__name__}"
_DistributedWrapped.__qualname__ = _DistributedWrapped.__name__
_DistributedWrapped.__doc__ = (
f"Distributed wrapper of :class:`{asm_cls.__name__}` produced by "
":func:`tensormesh.distributed.distributed`. See the decorator's "
"docstring for the new entry-point contract."
)
return _DistributedWrapped