• 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

60.46
/ufl/cell.py
1
"""Types for representing a cell."""
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
1✔
10

11
import functools
1✔
12
import numbers
1✔
13
import typing
1✔
14
import weakref
1✔
15
from abc import abstractmethod
1✔
16

17
from ufl.core.ufl_type import UFLObject
1✔
18

19
__all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"]
1✔
20

21

22
class AbstractCell(UFLObject):
1✔
23
    """A base class for all cells."""
24

25
    @property
1✔
26
    @abstractmethod
1✔
27
    def topological_dimension(self) -> int:
1✔
28
        """Return the dimension of the topology of this cell."""
29

30
    @property
1✔
31
    @abstractmethod
1✔
32
    def is_simplex(self) -> bool:
1✔
33
        """Return True if this is a simplex cell."""
34

35
    @property
1✔
36
    @abstractmethod
1✔
37
    def has_simplex_facets(self) -> bool:
1✔
38
        """Return True if all the facets of this cell are simplex cells."""
39

40
    @abstractmethod
1✔
41
    def _lt(self, other) -> bool:
1✔
42
        """Less than operator.
43

44
        Define an arbitrarily chosen but fixed sort order for all
45
        instances of this type with the same dimensions.
46
        """
47

48
    @abstractmethod
1✔
49
    def num_sub_entities(self, dim: int) -> int:
1✔
50
        """Get the number of sub-entities of the given dimension."""
51

52
    @abstractmethod
1✔
53
    def sub_entities(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
54
        """Get the sub-entities of the given dimension."""
55

56
    @abstractmethod
1✔
57
    def sub_entity_types(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
58
        """Get the unique sub-entity types of the given dimension."""
59

60
    @property
1✔
61
    @abstractmethod
1✔
62
    def cellname(self) -> str:
1✔
63
        """Return the cellname of the cell."""
64

65
    @abstractmethod
1✔
66
    def reconstruct(self, **kwargs: typing.Any) -> AbstractCell:
1✔
67
        """Reconstruct this cell, overwriting properties by those in kwargs."""
68

69
    def __lt__(self, other: AbstractCell) -> bool:
1✔
70
        """Define an arbitrarily chosen but fixed sort order for all cells."""
71
        if type(self) is type(other):
1✔
72
            s = self.topological_dimension
1✔
73
            o = other.topological_dimension
1✔
74
            if s != o:
1✔
75
                return s < o
1✔
76
            return self._lt(other)
1✔
77
        else:
78
            if type(self).__name__ == type(other).__name__:
×
79
                raise ValueError("Cannot order cell types with the same name")
×
80
            return type(self).__name__ < type(other).__name__
×
81

82
    @property
1✔
83
    def num_vertices(self) -> int:
1✔
84
        """Get the number of vertices."""
85
        return self.num_sub_entities(0)
1✔
86

87
    @property
1✔
88
    def num_edges(self) -> int:
1✔
89
        """Get the number of edges."""
90
        return self.num_sub_entities(1)
1✔
91

92
    @property
1✔
93
    def num_faces(self) -> int:
1✔
94
        """Get the number of faces."""
95
        return self.num_sub_entities(2)
1✔
96

97
    @property
1✔
98
    def num_facets(self) -> int:
1✔
99
        """Get the number of facets.
100

101
        Facets are entities of dimension tdim-1.
102
        """
103
        tdim = self.topological_dimension
1✔
104
        return self.num_sub_entities(tdim - 1)
1✔
105

106
    @property
1✔
107
    def num_ridges(self) -> int:
1✔
108
        """Get the number of ridges.
109

110
        Ridges are entities of dimension tdim-2.
111
        """
112
        tdim = self.topological_dimension
1✔
113
        return self.num_sub_entities(tdim - 2)
1✔
114

115
    @property
1✔
116
    def num_peaks(self) -> int:
1✔
117
        """Get the number of peaks.
118

119
        Peaks are entities of dimension tdim-3.
120
        """
121
        tdim = self.topological_dimension
1✔
122
        return self.num_sub_entities(tdim - 3)
1✔
123

124
    @property
1✔
125
    def vertices(self) -> tuple[AbstractCell, ...]:
1✔
126
        """Get the vertices."""
127
        return self.sub_entities(0)
×
128

129
    @property
1✔
130
    def edges(self) -> tuple[AbstractCell, ...]:
1✔
131
        """Get the edges."""
132
        return self.sub_entities(1)
×
133

134
    @property
1✔
135
    def faces(self) -> tuple[AbstractCell, ...]:
1✔
136
        """Get the faces."""
137
        return self.sub_entities(2)
×
138

139
    @property
1✔
140
    def facets(self) -> tuple[AbstractCell, ...]:
1✔
141
        """Get the facets.
142

143
        Facets are entities of dimension tdim-1.
144
        """
NEW
145
        tdim = self.topological_dimension
×
146
        return self.sub_entities(tdim - 1)
×
147

148
    @property
1✔
149
    def ridges(self) -> tuple[AbstractCell, ...]:
1✔
150
        """Get the ridges.
151

152
        Ridges are entities of dimension tdim-2.
153
        """
NEW
154
        tdim = self.topological_dimension
×
155
        return self.sub_entities(tdim - 2)
×
156

157
    @property
1✔
158
    def peaks(self) -> tuple[AbstractCell, ...]:
1✔
159
        """Get the peaks.
160

161
        Peaks are entities of dimension tdim-3.
162
        """
NEW
163
        tdim = self.topological_dimension
×
164
        return self.sub_entities(tdim - 3)
×
165

166
    @property
1✔
167
    def vertex_types(self) -> tuple[AbstractCell, ...]:
1✔
168
        """Get the unique vertices types."""
169
        return self.sub_entity_types(0)
×
170

171
    @property
1✔
172
    def edge_types(self) -> tuple[AbstractCell, ...]:
1✔
173
        """Get the unique edge types."""
174
        return self.sub_entity_types(1)
×
175

176
    @property
1✔
177
    def face_types(self) -> tuple[AbstractCell, ...]:
1✔
178
        """Get the unique face types."""
179
        return self.sub_entity_types(2)
×
180

181
    @property
1✔
182
    def facet_types(self) -> tuple[AbstractCell, ...]:
1✔
183
        """Get the unique facet types.
184

185
        Facets are entities of dimension tdim-1.
186
        """
NEW
187
        tdim = self.topological_dimension
×
188
        return self.sub_entity_types(tdim - 1)
×
189

190
    @property
1✔
191
    def ridge_types(self) -> tuple[AbstractCell, ...]:
1✔
192
        """Get the unique ridge types.
193

194
        Ridges are entities of dimension tdim-2.
195
        """
NEW
196
        tdim = self.topological_dimension
×
197
        return self.sub_entity_types(tdim - 2)
×
198

199
    @property
1✔
200
    def peak_types(self) -> tuple[AbstractCell, ...]:
1✔
201
        """Get the unique peak types.
202

203
        Peaks are entities of dimension tdim-3.
204
        """
NEW
205
        tdim = self.topological_dimension
×
206
        return self.sub_entity_types(tdim - 3)
×
207

208

209
_sub_entity_celltypes: dict[str, list[tuple[str, ...]]] = {
1✔
210
    "vertex": [("vertex",)],
211
    "interval": [tuple("vertex" for i in range(2)), ("interval",)],
212
    "triangle": [
213
        tuple("vertex" for i in range(3)),
214
        tuple("interval" for i in range(3)),
215
        ("triangle",),
216
    ],
217
    "quadrilateral": [
218
        tuple("vertex" for i in range(4)),
219
        tuple("interval" for i in range(4)),
220
        ("quadrilateral",),
221
    ],
222
    "tetrahedron": [
223
        tuple("vertex" for i in range(4)),
224
        tuple("interval" for i in range(6)),
225
        tuple("triangle" for i in range(4)),
226
        ("tetrahedron",),
227
    ],
228
    "hexahedron": [
229
        tuple("vertex" for i in range(8)),
230
        tuple("interval" for i in range(12)),
231
        tuple("quadrilateral" for i in range(6)),
232
        ("hexahedron",),
233
    ],
234
    "prism": [
235
        tuple("vertex" for i in range(6)),
236
        tuple("interval" for i in range(9)),
237
        ("triangle", "quadrilateral", "quadrilateral", "quadrilateral", "triangle"),
238
        ("prism",),
239
    ],
240
    "pyramid": [
241
        tuple("vertex" for i in range(5)),
242
        tuple("interval" for i in range(8)),
243
        ("quadrilateral", "triangle", "triangle", "triangle", "triangle"),
244
        ("pyramid",),
245
    ],
246
    "pentatope": [
247
        tuple("vertex" for i in range(5)),
248
        tuple("interval" for i in range(10)),
249
        tuple("triangle" for i in range(10)),
250
        tuple("tetrahedron" for i in range(5)),
251
        ("pentatope",),
252
    ],
253
    "tesseract": [
254
        tuple("vertex" for i in range(16)),
255
        tuple("interval" for i in range(32)),
256
        tuple("quadrilateral" for i in range(24)),
257
        tuple("hexahedron" for i in range(8)),
258
        ("tesseract",),
259
    ],
260
}
261

262

263
class Cell(AbstractCell):
1✔
264
    """Representation of a named finite element cell with known structure."""
265

266
    __slots__ = (
1✔
267
        "_cellname",
268
        "_num_cell_entities",
269
        "_sub_entities",
270
        "_sub_entity_types",
271
        "_sub_entity_types",
272
        "_tdim",
273
    )
274

275
    def __init__(self, cellname: str):
1✔
276
        """Initialise.
277

278
        Args:
279
            cellname: Name of the cell
280
        """
281
        if cellname not in _sub_entity_celltypes:
1✔
282
            raise ValueError(f"Unsupported cell type: {cellname}")
×
283

284
        self._sub_entity_celltypes = _sub_entity_celltypes[cellname]
1✔
285

286
        self._cellname = cellname
1✔
287
        self._tdim = len(self._sub_entity_celltypes) - 1
1✔
288

289
        self._num_cell_entities = [len(i) for i in self._sub_entity_celltypes]
1✔
290
        self._sub_entities = [
1✔
291
            tuple(Cell(t) for t in se_types) for se_types in self._sub_entity_celltypes[:-1]
292
        ]
293
        self._sub_entity_types = [tuple(set(i)) for i in self._sub_entities]
1✔
294
        self._sub_entities.append((weakref.proxy(self),))
1✔
295
        self._sub_entity_types.append((weakref.proxy(self),))
1✔
296

297
        if not isinstance(self._tdim, numbers.Integral):
1✔
298
            raise ValueError("Expecting integer topological_dimension.")
×
299

300
    @property
1✔
301
    def topological_dimension(self) -> int:
1✔
302
        """Return the dimension of the topology of this cell."""
303
        return self._tdim
1✔
304

305
    @property
1✔
306
    def is_simplex(self) -> bool:
1✔
307
        """Return True if this is a simplex cell."""
308
        return self._cellname in ["vertex", "interval", "triangle", "tetrahedron"]
1✔
309

310
    @property
1✔
311
    def has_simplex_facets(self) -> bool:
1✔
312
        """Return True if all the facets of this cell are simplex cells."""
313
        return self._cellname in ["interval", "triangle", "quadrilateral", "tetrahedron"]
1✔
314

315
    def num_sub_entities(self, dim: int) -> int:
1✔
316
        """Get the number of sub-entities of the given dimension."""
317
        if dim < 0:
1✔
318
            return 0
1✔
319
        try:
1✔
320
            return self._num_cell_entities[dim]
1✔
321
        except IndexError:
1✔
322
            return 0
1✔
323

324
    def sub_entities(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
325
        """Get the sub-entities of the given dimension."""
326
        if dim < 0:
×
327
            return ()
×
328
        try:
×
329
            return self._sub_entities[dim]
×
330
        except IndexError:
×
331
            return ()
×
332

333
    def sub_entity_types(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
334
        """Get the unique sub-entity types of the given dimension."""
335
        if dim < 0:
×
336
            return ()
×
337
        try:
×
338
            return self._sub_entity_types[dim]
×
339
        except IndexError:
×
340
            return ()
×
341

342
    def _lt(self, other) -> bool:
1✔
343
        return self._cellname < other._cellname
1✔
344

345
    @property
1✔
346
    def cellname(self) -> str:
1✔
347
        """Return the cellname of the cell."""
348
        return self._cellname
1✔
349

350
    def __str__(self) -> str:
1✔
351
        """Format as a string."""
352
        return self._cellname
1✔
353

354
    def __repr__(self) -> str:
1✔
355
        """Representation."""
356
        return self._cellname
×
357

358
    def _ufl_hash_data_(self) -> typing.Hashable:
1✔
359
        """UFL hash data."""
360
        return (self._cellname,)
1✔
361

362
    def reconstruct(self, **kwargs: typing.Any) -> Cell:
1✔
363
        """Reconstruct this cell, overwriting properties by those in kwargs."""
364
        for key, value in kwargs.items():
×
365
            raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'")
×
366
        return Cell(self._cellname)
×
367

368

369
class TensorProductCell(AbstractCell):
1✔
370
    """Tensor product cell."""
371

372
    __slots__ = ("_cells", "_tdim")
1✔
373

374
    def __init__(self, *cells: AbstractCell):
1✔
375
        """Initialise.
376

377
        Args:
378
            cells: Cells to take the tensor product of
379
        """
380
        self._cells = tuple(as_cell(cell) for cell in cells)
1✔
381

382
        self._tdim = sum([cell.topological_dimension for cell in self._cells])
1✔
383

384
        if not isinstance(self._tdim, numbers.Integral):
1✔
385
            raise ValueError("Expecting integer topological_dimension.")
×
386

387
    @property
1✔
388
    def sub_cells(self) -> tuple[AbstractCell, ...]:
1✔
389
        """Return list of cell factors."""
390
        return self._cells
1✔
391

392
    @property
1✔
393
    def topological_dimension(self) -> int:
1✔
394
        """Return the dimension of the topology of this cell."""
395
        return self._tdim
1✔
396

397
    @property
1✔
398
    def is_simplex(self) -> bool:
1✔
399
        """Return True if this is a simplex cell."""
400
        if len(self._cells) == 1:
×
NEW
401
            return self._cells[0].is_simplex
×
402
        return False
×
403

404
    @property
1✔
405
    def has_simplex_facets(self) -> bool:
1✔
406
        """Return True if all the facets of this cell are simplex cells."""
407
        if len(self._cells) == 1:
×
NEW
408
            return self._cells[0].has_simplex_facets
×
409
        if self._tdim == 1:
×
410
            return True
×
411
        return False
×
412

413
    def num_sub_entities(self, dim: int) -> int:
1✔
414
        """Get the number of sub-entities of the given dimension."""
415
        if dim < 0 or dim > self._tdim:
×
416
            return 0
×
417
        if dim == 0:
×
NEW
418
            return functools.reduce(lambda x, y: x * y, [c.num_vertices for c in self._cells])
×
419
        if dim == self._tdim - 1:
×
420
            # Note: This is not the number of facets that the cell has,
421
            # but I'm leaving it here for now to not change past
422
            # behaviour
NEW
423
            return sum(c.num_facets for c in self._cells if c.topological_dimension > 0)
×
424
        if dim == self._tdim:
×
425
            return 1
×
426
        raise NotImplementedError(f"TensorProductCell.num_sub_entities({dim}) is not implemented.")
×
427

428
    def sub_entities(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
429
        """Get the sub-entities of the given dimension."""
430
        if dim < 0 or dim > self._tdim:
×
431
            return ()
×
432
        if dim == 0:
×
433
            return tuple(Cell("vertex") for i in range(self.num_sub_entities(0)))
×
434
        if dim == self._tdim:
×
435
            return (self,)
×
436
        raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.")
×
437

438
    def sub_entity_types(self, dim: int) -> tuple[AbstractCell, ...]:
1✔
439
        """Get the unique sub-entity types of the given dimension."""
440
        if dim < 0 or dim > self._tdim:
×
441
            return ()
×
442
        if dim == 0:
×
443
            return (Cell("vertex"),)
×
444
        if dim == self._tdim:
×
445
            return (self,)
×
446
        raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.")
×
447

448
    def _lt(self, other) -> bool:
1✔
449
        return self._ufl_hash_data_() < other._ufl_hash_data_()
×
450

451
    @property
1✔
452
    def cellname(self) -> str:
1✔
453
        """Return the cellname of the cell."""
NEW
454
        return " * ".join([cell.cellname for cell in self._cells])
×
455

456
    def __str__(self) -> str:
1✔
457
        """Format as a string."""
458
        return "TensorProductCell(" + ", ".join(f"{c!r}" for c in self._cells) + ")"
×
459

460
    def __repr__(self) -> str:
1✔
461
        """Representation."""
462
        return str(self)
×
463

464
    def _ufl_hash_data_(self) -> typing.Hashable:
1✔
465
        """UFL hash data."""
466
        return tuple(c._ufl_hash_data_() for c in self._cells)
×
467

468
    def reconstruct(self, **kwargs: typing.Any) -> AbstractCell:
1✔
469
        """Reconstruct this cell, overwriting properties by those in kwargs."""
470
        for key, value in kwargs.items():
1✔
471
            raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'")
×
472
        return TensorProductCell(*self._cells)
1✔
473

474

475
def simplex(topological_dimension: int):
1✔
476
    """Return a simplex cell of the given dimension."""
477
    if topological_dimension == 0:
×
478
        return Cell("vertex")
×
479
    if topological_dimension == 1:
×
480
        return Cell("interval")
×
481
    if topological_dimension == 2:
×
482
        return Cell("triangle")
×
483
    if topological_dimension == 3:
×
484
        return Cell("tetrahedron")
×
485
    if topological_dimension == 4:
×
486
        return Cell("pentatope")
×
487
    raise ValueError(f"Unsupported topological dimension for simplex: {topological_dimension}")
×
488

489

490
def hypercube(topological_dimension: int):
1✔
491
    """Return a hypercube cell of the given dimension."""
492
    if topological_dimension == 0:
×
493
        return Cell("vertex")
×
494
    if topological_dimension == 1:
×
495
        return Cell("interval")
×
496
    if topological_dimension == 2:
×
497
        return Cell("quadrilateral")
×
498
    if topological_dimension == 3:
×
499
        return Cell("hexahedron")
×
500
    if topological_dimension == 4:
×
501
        return Cell("tesseract")
×
502
    raise ValueError(f"Unsupported topological dimension for hypercube: {topological_dimension}")
×
503

504

505
def as_cell(cell: AbstractCell | str | tuple[AbstractCell, ...]) -> AbstractCell:
1✔
506
    """Convert any valid object to a Cell or return cell if it is already a Cell.
507

508
    Allows an already valid cell, a known cellname string, or a tuple of cells for a product cell.
509
    """
510
    if isinstance(cell, AbstractCell):
1✔
511
        return cell
1✔
512
    elif isinstance(cell, str):
×
513
        return Cell(cell)
×
514
    elif isinstance(cell, tuple):
×
515
        return TensorProductCell(*cell)
×
516
    else:
517
        raise ValueError(f"Invalid cell {cell}.")
×
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