• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FEniCS / ufl / 17702620171

13 Sep 2025 10:07PM UTC coverage: 75.985% (+0.07%) from 75.917%
17702620171

Pull #385

github

schnellerhase
One more
Pull Request #385: Change `AbstractCell` members to properties

80 of 121 new or added lines in 11 files covered. (66.12%)

5 existing lines in 1 file now uncovered.

8970 of 11805 relevant lines covered (75.98%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.34
/ufl/domain.py
1
"""Types for representing a geometric domain."""
2

3
# Copyright (C) 2008-2016 Martin Sandve Alnæs
4
#
5
# This file is part of UFL (https://www.fenicsproject.org)
6
#
7
# SPDX-License-Identifier:    LGPL-3.0-or-later
8

9
from __future__ import annotations  # To avoid cyclic import when type-hinting.
1✔
10

11
import numbers
1✔
12
from collections.abc import Iterable, Sequence
1✔
13
from typing import TYPE_CHECKING
1✔
14

15
if TYPE_CHECKING:
16
    from ufl.core.expr import Expr
17
    from ufl.finiteelement import AbstractFiniteElement  # To avoid cyclic import when type-hinting.
18
    from ufl.form import Form
19
from ufl.cell import AbstractCell
1✔
20
from ufl.core.ufl_id import attach_ufl_id
1✔
21
from ufl.core.ufl_type import UFLObject
1✔
22
from ufl.corealg.traversal import traverse_unique_terminals
1✔
23
from ufl.sobolevspace import H1
1✔
24

25
# Export list for ufl.classes
26
__all_classes__ = ["AbstractDomain", "Mesh", "MeshView"]
1✔
27

28

29
class AbstractDomain:
1✔
30
    """Symbolic representation of a geometric domain.
31

32
    Domain has only a geometric and a topological dimension.
33
    """
34

35
    def __init__(self, topological_dimension, geometric_dimension):
1✔
36
        """Initialise."""
37
        # Validate dimensions
38
        if not isinstance(geometric_dimension, numbers.Integral):
1✔
39
            raise ValueError(
×
40
                f"Expecting integer geometric dimension, not {geometric_dimension.__class__}"
41
            )
42
        if not isinstance(topological_dimension, numbers.Integral):
1✔
43
            raise ValueError(
×
44
                f"Expecting integer topological dimension, not {topological_dimension.__class__}"
45
            )
46
        if topological_dimension > geometric_dimension:
1✔
47
            raise ValueError("Topological dimension cannot be larger than geometric dimension.")
×
48

49
        # Store validated dimensions
50
        self._topological_dimension = topological_dimension
1✔
51
        self._geometric_dimension = geometric_dimension
1✔
52

53
    @property
1✔
54
    def geometric_dimension(self):
1✔
55
        """Return the dimension of the space this domain is embedded in."""
56
        return self._geometric_dimension
1✔
57

58
    @property
1✔
59
    def topological_dimension(self):
1✔
60
        """Return the dimension of the topology of this domain."""
61
        return self._topological_dimension
1✔
62

63
    @property
1✔
64
    def meshes(self):
1✔
65
        """Return the component meshes."""
66
        raise NotImplementedError("meshes() method not implemented")
×
67

68
    def __len__(self):
1✔
69
        """Return number of component meshes."""
70
        return len(self.meshes)
1✔
71

72
    def __getitem__(self, i):
1✔
73
        """Return i-th component mesh."""
74
        if i >= len(self):
1✔
75
            raise ValueError(f"index ({i}) >= num. component meshes ({len(self)})")
×
76
        return self.meshes[i]
1✔
77

78
    def __iter__(self):
1✔
79
        """Return iterable component meshes."""
80
        return iter(self.meshes)
1✔
81

82
    def iterable_like(self, element: AbstractFiniteElement) -> Iterable[Mesh] | MeshSequence:
1✔
83
        """Return iterable object that is iterable like ``element``."""
84
        raise NotImplementedError("iterable_like() method not implemented")
×
85

86
    def can_make_function_space(self, element: AbstractFiniteElement) -> bool:
1✔
87
        """Check whether this mesh can make a function space with ``element``."""
88
        raise NotImplementedError("can_make_function_space() method not implemented")
×
89

90

91
# TODO: Would it be useful to have a domain representing R^d? E.g. for
92
# Expression.
93
# class EuclideanSpace(AbstractDomain):
94
#     def __init__(self, geometric_dimension):
95
#         AbstractDomain.__init__(self, geometric_dimension, geometric_dimension)
96

97

98
@attach_ufl_id
1✔
99
class Mesh(AbstractDomain, UFLObject):
1✔
100
    """Symbolic representation of a mesh."""
101

102
    def __init__(self, coordinate_element, ufl_id=None, cargo=None):
1✔
103
        """Initialise."""
104
        self._ufl_id = self._init_ufl_id(ufl_id)
1✔
105

106
        # Store reference to object that will not be used by UFL
107
        self._ufl_cargo = cargo
1✔
108
        if cargo is not None and cargo.ufl_id() != self._ufl_id:
1✔
109
            raise ValueError("Expecting cargo object (e.g. dolfin.Mesh) to have the same ufl_id.")
1✔
110

111
        # No longer accepting coordinates provided as a Coefficient
112
        from ufl.coefficient import Coefficient
1✔
113

114
        if isinstance(coordinate_element, Coefficient | AbstractCell):
1✔
115
            raise ValueError("Expecting a coordinate element in the ufl.Mesh construct.")
1✔
116

117
        # Store coordinate element
118
        self._ufl_coordinate_element = coordinate_element
1✔
119

120
        # Derive dimensions from element
121
        (gdim,) = coordinate_element.reference_value_shape
1✔
122
        tdim = coordinate_element.cell.topological_dimension
1✔
123
        AbstractDomain.__init__(self, tdim, gdim)
1✔
124

125
    def ufl_cargo(self):
1✔
126
        """Return carried object that will not be used by UFL."""
127
        return self._ufl_cargo
1✔
128

129
    def ufl_coordinate_element(self):
1✔
130
        """Get the coordinate element."""
131
        return self._ufl_coordinate_element
1✔
132

133
    def ufl_cell(self):
1✔
134
        """Get the cell."""
135
        return self._ufl_coordinate_element.cell
1✔
136

137
    def is_piecewise_linear_simplex_domain(self):
1✔
138
        """Check if the domain is a piecewise linear simplex."""
139
        ce = self._ufl_coordinate_element
1✔
140
        return ce.embedded_superdegree <= 1 and ce in H1 and self.ufl_cell().is_simplex
1✔
141

142
    def __repr__(self):
1✔
143
        """Representation."""
144
        r = f"Mesh({self._ufl_coordinate_element!r}, {self._ufl_id!r})"
1✔
145
        return r
1✔
146

147
    def __str__(self):
1✔
148
        """Format as a string."""
149
        return f"<Mesh #{self._ufl_id}>"
1✔
150

151
    def _ufl_hash_data_(self):
1✔
152
        """UFL hash data."""
153
        return (self._ufl_id, self._ufl_coordinate_element)
1✔
154

155
    def _ufl_signature_data_(self, renumbering):
1✔
156
        """UFL signature data."""
157
        return ("Mesh", renumbering[self], self._ufl_coordinate_element)
1✔
158

159
    # NB! Dropped __lt__ here, don't want users to write 'mesh1 <
160
    # mesh2'.
161
    def _ufl_sort_key_(self):
1✔
162
        """UFL sort key."""
163
        typespecific = (self._ufl_id, self._ufl_coordinate_element)
1✔
164
        return (self.geometric_dimension, self.topological_dimension, "Mesh", typespecific)
1✔
165

166
    @property
1✔
167
    def meshes(self):
1✔
168
        """Return the component meshes."""
169
        return (self,)
1✔
170

171
    def iterable_like(self, element: AbstractFiniteElement) -> Iterable[Mesh]:
1✔
172
        """Return iterable object that is iterable like ``element``."""
173
        return iter(self for _ in range(element.num_sub_elements))
1✔
174

175
    def can_make_function_space(self, element: AbstractFiniteElement) -> bool:
1✔
176
        """Check whether this mesh can make a function space with ``element``."""
177
        # Can use with any element.
178
        return True
1✔
179

180

181
class MeshSequence(AbstractDomain, UFLObject):
1✔
182
    """Symbolic representation of a mixed mesh.
183

184
    This class represents a collection of meshes that, along with
185
    a :class:`MixedElement`, represent a mixed function space defined on
186
    multiple domains. This abstraction allows for defining the
187
    mixed function space with the conventional :class:`FunctionSpace`
188
    class and integrating multi-domain problems seamlessly.
189

190
    Currently, all component meshes must have the same cell type (and
191
    thus the same topological dimension).
192

193
    Currently, one can only perform cell integrations when
194
    :class:`MeshSequence` is used.
195

196
    .. code-block:: python3
197

198
        cell = triangle
199
        mesh0 = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1))
200
        mesh1 = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1))
201
        domain = MeshSequence([mesh0, mesh1])
202
        elem0 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1)
203
        elem1 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1)
204
        elem = MixedElement([elem0, elem1])
205
        V = FunctionSpace(domain, elem)
206
        v = TestFunction(V)
207
        v0, v1 = split(v)
208

209
    """
210

211
    def __init__(self, meshes: Sequence[Mesh]):
1✔
212
        """Initialise."""
213
        if any(isinstance(m, MeshSequence) for m in meshes):
1✔
214
            raise NotImplementedError("""
×
215
                Currently component meshes can not include MeshSequence instances""")
216
        # currently only support single cell type.
217
        (self._ufl_cell,) = set(m.ufl_cell() for m in meshes)
1✔
218
        (gdim,) = set(m.geometric_dimension for m in meshes)
1✔
219
        # TODO: Need to change for more general mixed meshes.
220
        (tdim,) = set(m.topological_dimension for m in meshes)
1✔
221
        AbstractDomain.__init__(self, tdim, gdim)
1✔
222
        self._meshes = tuple(meshes)
1✔
223

224
    def ufl_cell(self):
1✔
225
        """Get the cell."""
226
        # TODO: Might need MixedCell class for more general mixed meshes.
227
        return self._ufl_cell
1✔
228

229
    def __repr__(self):
1✔
230
        """Representation."""
231
        return f"MeshSequence({self._meshes!r})"
1✔
232

233
    def __str__(self):
1✔
234
        """Format as a string."""
235
        return f"<MeshSequence #{self._meshes}>"
1✔
236

237
    def _ufl_hash_data_(self):
1✔
238
        """UFL hash data."""
239
        return ("MeshSequence", tuple(m._ufl_hash_data_() for m in self._meshes))
1✔
240

241
    def _ufl_signature_data_(self, renumbering):
1✔
242
        """UFL signature data."""
243
        return ("MeshSequence", tuple(m._ufl_signature_data_(renumbering) for m in self._meshes))
×
244

245
    def _ufl_sort_key_(self):
1✔
246
        """UFL sort key."""
247
        return ("MeshSequence", tuple(m._ufl_sort_key_() for m in self._meshes))
1✔
248

249
    @property
1✔
250
    def meshes(self):
1✔
251
        """Return the component meshes."""
252
        return self._meshes
1✔
253

254
    def iterable_like(self, element: AbstractFiniteElement) -> MeshSequence:
1✔
255
        """Return iterable object that is iterable like ``element``."""
256
        if len(self) != element.num_sub_elements:
1✔
257
            raise RuntimeError(f"""len(self) ({len(self)}) !=
×
258
                element.num_sub_elements ({element.num_sub_elements})""")
259
        return self
1✔
260

261
    def can_make_function_space(self, element: AbstractFiniteElement) -> bool:
1✔
262
        """Check whether this mesh can make a function space with ``element``."""
263
        if len(self) != element.num_sub_elements:
1✔
264
            return False
×
265
        else:
266
            return all(d.can_make_function_space(e) for d, e in zip(self, element.sub_elements))
1✔
267

268

269
@attach_ufl_id
1✔
270
class MeshView(AbstractDomain, UFLObject):
1✔
271
    """Symbolic representation of a mesh."""
272

273
    def __init__(self, mesh, topological_dimension, ufl_id=None):
1✔
274
        """Initialise."""
275
        self._ufl_id = self._init_ufl_id(ufl_id)
×
276

277
        # Store mesh
278
        self._ufl_mesh = mesh
×
279

280
        # Derive dimensions from element
281
        coordinate_element = mesh.ufl_coordinate_element()
×
282
        (gdim,) = coordinate_element.value_shape
×
NEW
283
        tdim = coordinate_element.cell.topological_dimension
×
284
        AbstractDomain.__init__(self, tdim, gdim)
×
285

286
    def ufl_mesh(self):
1✔
287
        """Get the mesh."""
288
        return self._ufl_mesh
×
289

290
    def ufl_cell(self):
1✔
291
        """Get the cell."""
292
        return self._ufl_mesh.ufl_cell()
×
293

294
    def is_piecewise_linear_simplex_domain(self):
1✔
295
        """Check if the domain is a piecewise linear simplex."""
296
        return self._ufl_mesh.is_piecewise_linear_simplex_domain()
×
297

298
    def __repr__(self):
1✔
299
        """Representation."""
300
        tdim = self.topological_dimension()
×
301
        r = f"MeshView({self._ufl_mesh!r}, {tdim!r}, {self._ufl_id!r})"
×
302
        return r
×
303

304
    def __str__(self):
1✔
305
        """Format as a string."""
306
        return (
×
307
            f"<MeshView #{self._ufl_id} of dimension {self.topological_dimension()} over"
308
            f" mesh {self._ufl_mesh}>"
309
        )
310

311
    def _ufl_hash_data_(self):
1✔
312
        """UFL hash data."""
313
        return (self._ufl_id,) + self._ufl_mesh._ufl_hash_data_()
×
314

315
    def _ufl_signature_data_(self, renumbering):
1✔
316
        """UFL signature data."""
317
        return ("MeshView", renumbering[self], self._ufl_mesh._ufl_signature_data_(renumbering))
×
318

319
    # NB! Dropped __lt__ here, don't want users to write 'mesh1 <
320
    # mesh2'.
321
    def _ufl_sort_key_(self):
1✔
322
        """UFL sort key."""
323
        typespecific = (self._ufl_id, self._ufl_mesh)
×
324
        return (self.geometric_dimension(), self.topological_dimension(), "MeshView", typespecific)
×
325

326

327
def as_domain(domain):
1✔
328
    """Convert any valid object to an AbstractDomain type."""
329
    if isinstance(domain, AbstractDomain):
1✔
330
        # Modern UFL files and dolfin behaviour
331
        (domain,) = set(domain.meshes)
1✔
332
        return domain
1✔
333
    try:
1✔
334
        return extract_unique_domain(domain)
1✔
335
    except AttributeError:
1✔
336
        domain = domain.ufl_domain()
1✔
337
        (domain,) = set(domain.meshes)
1✔
338
        return domain
1✔
339

340

341
def sort_domains(domains: Sequence[AbstractDomain]):
1✔
342
    """Sort domains in a canonical ordering.
343

344
    Args:
345
        domains: Sequence of domains.
346

347
    Returns:
348
        `tuple` of sorted domains.
349

350
    """
351
    return tuple(sorted(domains, key=lambda domain: domain._ufl_sort_key_()))
1✔
352

353

354
def join_domains(domains: Sequence[AbstractDomain], expand_mesh_sequence: bool = True):
1✔
355
    """Take a list of domains and return a set with only unique domain objects.
356

357
    Args:
358
        domains: Sequence of domains.
359
        expand_mesh_sequence: If True, MeshSequence components are expanded.
360

361
    Returns:
362
        `set` of domains.
363

364
    """
365
    # Use hashing to join domains, ignore None
366
    joined_domains = set(domains) - set((None,))
1✔
367
    if expand_mesh_sequence:
1✔
368
        unrolled_joined_domains = set()
1✔
369
        for domain in joined_domains:
1✔
370
            unrolled_joined_domains.update(domain.meshes)
1✔
371
        joined_domains = unrolled_joined_domains
1✔
372

373
    if not joined_domains:
1✔
374
        return ()
1✔
375

376
    # Check geometric dimension compatibility
377
    gdims = set()
1✔
378
    for domain in joined_domains:
1✔
379
        gdims.add(domain.geometric_dimension)
1✔
380
    if len(gdims) != 1:
1✔
381
        raise ValueError("Found domains with different geometric dimensions.")
1✔
382

383
    return joined_domains
1✔
384

385

386
# TODO: Move these to an analysis module?
387

388

389
def extract_domains(expr: Expr | Form, expand_mesh_sequence: bool = True):
1✔
390
    """Return all domains expression is defined on.
391

392
    Args:
393
        expr: Expr or Form.
394
        expand_mesh_sequence: If True, MeshSequence components are expanded.
395

396
    Returns:
397
        `tuple` of domains.
398

399
    """
400
    from ufl.form import Form
1✔
401

402
    if isinstance(expr, Form):
1✔
403
        if not expand_mesh_sequence:
1✔
404
            raise NotImplementedError("""
×
405
                Currently, can only extract domains from a Form with expand_mesh_sequence=True""")
406
        # Be consistent with the numbering used in signature.
407
        return tuple(expr.domain_numbering().keys())
1✔
408
    else:
409
        domainlist = []
1✔
410
        for t in traverse_unique_terminals(expr):
1✔
411
            domainlist.extend(t.ufl_domains())
1✔
412
        return sort_domains(join_domains(domainlist, expand_mesh_sequence=expand_mesh_sequence))
1✔
413

414

415
def extract_unique_domain(expr, expand_mesh_sequence: bool = True):
1✔
416
    """Return the single unique domain expression is defined on or throw an error.
417

418
    Args:
419
        expr: Expr or Form.
420
        expand_mesh_sequence: If True, MeshSequence components are expanded.
421

422
    Returns:
423
        domain.
424

425
    """
426
    domains = extract_domains(expr, expand_mesh_sequence=expand_mesh_sequence)
1✔
427
    if len(domains) == 1:
1✔
428
        return domains[0]
1✔
429
    elif domains:
1✔
430
        raise ValueError("Found multiple domains, cannot return just one.")
×
431
    else:
432
        return None
1✔
433

434

435
def find_geometric_dimension(expr):
1✔
436
    """Find the geometric dimension of an expression."""
437
    gdims = set()
1✔
438
    for t in traverse_unique_terminals(expr):
1✔
439
        # Can have multiple domains of the same cell type.
440
        domains = extract_domains(t)
1✔
441
        if len(domains) > 0:
1✔
442
            (gdim,) = set(domain.geometric_dimension for domain in domains)
1✔
443
            gdims.add(gdim)
1✔
444

445
    if len(gdims) != 1:
1✔
446
        raise ValueError("Cannot determine geometric dimension from expression.")
×
447
    (gdim,) = gdims
1✔
448
    return gdim
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc