• 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

74.76
/ufl/geometry.py
1
"""Types for representing symbolic expressions for geometric quantities."""
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 ufl.core.terminal import Terminal
1✔
10
from ufl.core.ufl_type import ufl_type
1✔
11
from ufl.domain import MeshSequence, as_domain, extract_unique_domain
1✔
12
from ufl.sobolevspace import H1
1✔
13

14
"""
1✔
15
Possible coordinate bootstrapping:
16

17
Xf = Xf[q]
18
    FacetCoordinate = quadrature point on facet (ds,dS)
19

20
X = X[q]
21
    CellCoordinate = quadrature point on cell (dx)
22

23
x = x[q]
24
    SpatialCoordinate = quadrature point from input array (dc)
25

26
Xe = Xe[q]
27
    RidgeCoordinate = quadrature point on ridge (dr)
28

29
Jacobians of mappings between coordinates:
30

31
Jcf = dX/dXf = grad_Xf X(Xf)
32
    CellFacetJacobian
33

34
Jxc = dx/dX = grad_X x(X)
35
    Jacobian
36

37
Jxf = dx/dXf = grad_Xf x(Xf)  =  Jxc Jcf = dx/dX dX/dXf = grad_X x(X) grad_Xf X(Xf)
38
    FacetJacobian = Jacobian * CellFacetJacobian
39

40
Jcr = dX/dXe = grad_Xe X(Xe)
41
    CellRidgeJacobian
42

43
Jfe = dXf/dXe = grad_Xe Xf(Xe)
44
    FacetRidgeJacobian
45

46
Jxe = dx/dXe = grad_Xe x(Xe) = Jxc Jcr = dx/dX dX/dXe =  grad_X x(X) grad_Xe X(Xe)
47
    RidgeJacobian = Jacobian * CellRidgeJacobian
48
# NOTE : Jxe could also be defined from CellFacetJacobian and FacetRidgeJacobian
49
# Jxe(v2) =  dx/dXe = grad_Xe x(Xe) = Jxc Jcf Jfe = dx/dX dX/dXf dXf/dXe
50
# = grad_X x(X) grad_Xf X(Xf) grad_Xe Xf(Xe)
51
#    RidgeJacobian = Jacobian * CellFacetJacobian * FacetRidgeJacobian
52

53
Possible computation of X from Xf:
54

55
X = Jcf Xf + X0f
56
    CellCoordinate = CellFacetJacobian * FacetCoordinate + CellFacetOrigin
57

58
Possible computation of X and Xf from Xe:
59

60
X = Jcr Xe + X0e
61
    CellCoordinate = CellRidgeJacobian * RidgeCoordinate + CellRidgeOrigin
62

63
Xf = Jfe Xe + X0e and X = Jcf (Jfe Xe + X0e) + X0f
64
    FacetCoordinate = FacetRidgeJacobian * RidgeCoordinate + CellRidgeOrigin
65

66
Possible computation of x from X:
67

68
x = f(X)
69
    SpatialCoordinate = sum_k xdofs_k * xphi_k(X)
70

71
x = Jxc X + x0
72
    SpatialCoordinate = Jacobian * CellCoordinate + CellOrigin
73

74

75
Possible computation of x from Xf:
76

77
x = x(X(Xf))
78

79
x = Jxf Xf + x0f
80
    SpatialCoordinate = FacetJacobian * FacetCoordinate + FacetOrigin
81

82
Possible computation of x from Xe:
83

84
x = x(X(Xe)) = Jxe Xe + x0e
85
    SpatialCoordinate = RidgeJacobian * RidgeCoordinate + RidgeOrigin
86

87
Inverse relations:
88

89
X = K * (x - x0)
90
    CellCoordinate = JacobianInverse * (SpatialCoordinate - CellOrigin)
91

92
Xf = FK * (x - x0f)
93
    FacetCoordinate = FacetJacobianInverse * (SpatialCoordinate - FacetOrigin)
94

95
Xf = CFK * (X - X0f)
96
    FacetCoordinate = CellFacetJacobianInverse * \
97
        (CellCoordinate - CellFacetOrigin)
98

99
Xe = EK * (x - x0e)
100
    RidgeCoordinate = RidgeJacobianInverse * (SpatialCoordinate - RidgeOrigin)
101

102
Xe = CEK * (X - X0e)
103
    RidgeCoordinate = CellRidgeJacobianInverse * \
104
        (CellCoordinate - CellRidgeOrigin)
105
"""
106

107
# --- Expression node types
108

109

110
@ufl_type(is_abstract=True)
1✔
111
class GeometricQuantity(Terminal):
1✔
112
    """Geometric quantity."""
113

114
    __slots__ = ("_domain",)
1✔
115

116
    def __init__(self, domain):
1✔
117
        """Initialise."""
118
        Terminal.__init__(self)
1✔
119
        if isinstance(domain, MeshSequence) and len(set(domain)) > 1:
1✔
120
            # Can not make GeometricQuantity if multiple domains exist.
121
            raise TypeError(f"Can not create a GeometricQuantity on {domain}")
×
122
        self._domain = as_domain(domain)
1✔
123

124
    def ufl_domains(self):
1✔
125
        """Get the UFL domains."""
126
        return (self._domain,)
1✔
127

128
    def is_cellwise_constant(self):
1✔
129
        """Return whether this expression is spatially constant over each cell."""
130
        # NB! Geometric quantities are piecewise constant by
131
        # default. Override if needed.
132
        return True
1✔
133

134
    # NB! Geometric quantities are scalar by default. Override if
135
    # needed.
136
    ufl_shape = ()
1✔
137

138
    def _ufl_signature_data_(self, renumbering):
1✔
139
        """Signature data of geometric quantities depend on the domain numbering."""
140
        return (self._ufl_class_.__name__,) + self._domain._ufl_signature_data_(renumbering)
1✔
141

142
    def __str__(self):
1✔
143
        """Format as a string."""
144
        return self._ufl_class_.name
1✔
145

146
    def __repr__(self):
1✔
147
        """Representation."""
148
        r = f"{self._ufl_class_.__name__}({self._domain!r})"
1✔
149
        return r
1✔
150

151
    def _ufl_compute_hash_(self):
1✔
152
        """UFL compute hash."""
153
        return hash((type(self).__name__,) + self._domain._ufl_hash_data_())
1✔
154

155
    def __eq__(self, other):
1✔
156
        """Check equality."""
157
        return isinstance(other, self._ufl_class_) and other._domain == self._domain
1✔
158

159

160
@ufl_type(is_abstract=True)
1✔
161
class GeometricCellQuantity(GeometricQuantity):
1✔
162
    """Geometric cell quantity."""
163

164
    __slots__ = ()
1✔
165

166

167
@ufl_type(is_abstract=True)
1✔
168
class GeometricFacetQuantity(GeometricQuantity):
1✔
169
    """Geometric facet quantity."""
170

171
    __slots__ = ()
1✔
172

173

174
@ufl_type(is_abstract=True)
1✔
175
class GeometricRidgeQuantity(GeometricQuantity):
1✔
176
    """Geometric ridge quantity."""
177

178
    __slots__ = ()
1✔
179

180

181
# --- Coordinate represented in different coordinate systems
182

183

184
@ufl_type()
1✔
185
class SpatialCoordinate(GeometricCellQuantity):
1✔
186
    """The coordinate in a domain.
187

188
    In the context of expression integration,
189
    represents the domain coordinate of each quadrature point.
190

191
    In the context of expression evaluation in a point,
192
    represents the value of that point.
193
    """
194

195
    __slots__ = ()
1✔
196
    name = "x"
1✔
197

198
    @property
1✔
199
    def ufl_shape(self):
1✔
200
        """Return the number of coordinates defined (i.e. the geometric dimension of the domain)."""
201
        g = self._domain.geometric_dimension
1✔
202
        return (g,)
1✔
203

204
    def is_cellwise_constant(self):
1✔
205
        """Return whether this expression is spatially constant over each cell."""
206
        # Only case this is true is if the domain is a vertex cell.
207
        t = self._domain.topological_dimension
1✔
208
        return t == 0
1✔
209

210
    def evaluate(self, x, mapping, component, index_values):
1✔
211
        """Return the value of the coordinate."""
212
        if component == ():
1✔
213
            if isinstance(x, tuple | list):
×
214
                return float(x[0])
×
215
            else:
216
                return float(x)
×
217
        else:
218
            return float(x[component[0]])
1✔
219

220
    def count(self):
1✔
221
        """Count."""
222
        # FIXME: Hack to make SpatialCoordinate behave like a coefficient.
223
        # When calling `derivative`, the count is used to sort over.
224
        return -1
×
225

226

227
@ufl_type()
1✔
228
class CellCoordinate(GeometricCellQuantity):
1✔
229
    """The coordinate in a reference cell.
230

231
    In the context of expression integration,
232
    represents the reference cell coordinate of each quadrature point.
233

234
    In the context of expression evaluation in a point in a cell,
235
    represents that point in the reference coordinate system of the cell.
236
    """
237

238
    __slots__ = ()
1✔
239
    name = "X"
1✔
240

241
    @property
1✔
242
    def ufl_shape(self):
1✔
243
        """Get the UFL shape."""
244
        t = self._domain.topological_dimension
1✔
245
        return (t,)
1✔
246

247
    def is_cellwise_constant(self):
1✔
248
        """Return whether this expression is spatially constant over each cell."""
249
        # Only case this is true is if the domain is a vertex cell.
250
        t = self._domain.topological_dimension
1✔
251
        return t == 0
1✔
252

253

254
@ufl_type()
1✔
255
class FacetCoordinate(GeometricFacetQuantity):
1✔
256
    """The coordinate in a reference cell of a facet.
257

258
    In the context of expression integration over a facet,
259
    represents the reference facet coordinate of each quadrature point.
260

261
    In the context of expression evaluation in a point on a facet,
262
    represents that point in the reference coordinate system of the facet.
263
    """
264

265
    __slots__ = ()
1✔
266
    name = "Xf"
1✔
267

268
    def __init__(self, domain):
1✔
269
        """Initialise."""
270
        GeometricFacetQuantity.__init__(self, domain)
×
271
        t = self._domain.topological_dimension()
×
272
        if t < 2:
×
273
            raise ValueError("FacetCoordinate is only defined for topological dimensions >= 2.")
×
274

275
    @property
1✔
276
    def ufl_shape(self):
1✔
277
        """Get the UFL shape."""
278
        t = self._domain.topological_dimension()
×
279
        return (t - 1,)
×
280

281
    def is_cellwise_constant(self):
1✔
282
        """Return whether this expression is spatially constant over each cell."""
283
        # Only case this is true is if the domain is an interval cell
284
        # (with a vertex facet).
285
        t = self._domain.topological_dimension()
×
286
        return t <= 1
×
287

288

289
@ufl_type()
1✔
290
class RidgeCoordinate(GeometricRidgeQuantity):
1✔
291
    """The coordinate in a reference cell of a ridge.
292

293
    In the context of expression integration over a ridge,
294
    represents the reference ridge coordinate of each quadrature point.
295

296
    In the context of expression evaluation in a point on a ridge,
297
    represents that point in the reference coordinate system of the ridge.
298
    """
299

300
    __slots__ = ()
1✔
301
    name = "Xe"
1✔
302

303
    def __init__(self, domain):
1✔
304
        """Initialise."""
305
        GeometricRidgeQuantity.__init__(self, domain)
×
306
        t = self._domain.topological_dimension()
×
307
        if t < 2:
×
308
            raise ValueError("RidgeCoordinate is only defined for topological dimensions >= 2.")
×
309

310
    @property
1✔
311
    def ufl_shape(self):
1✔
312
        """Get the UFL shape."""
313
        t = self._domain.topological_dimension()
×
314
        return (t - 2,)
×
315

316
    def is_cellwise_constant(self):
1✔
317
        """Return whether this expression is spatially constant over each cell."""
318
        return False
×
319

320

321
# --- Origin of coordinate systems in larger coordinate systems
322

323

324
@ufl_type()
1✔
325
class CellOrigin(GeometricCellQuantity):
1✔
326
    """The spatial coordinate corresponding to origin of a reference cell."""
327

328
    __slots__ = ()
1✔
329
    name = "x0"
1✔
330

331
    @property
1✔
332
    def ufl_shape(self):
1✔
333
        """Get the UFL shape."""
334
        g = self._domain.geometric_dimension()
×
335
        return (g,)
×
336

337
    def is_cellwise_constant(self):
1✔
338
        """Return whether this expression is spatially constant over each cell."""
339
        return True
×
340

341

342
@ufl_type()
1✔
343
class FacetOrigin(GeometricFacetQuantity):
1✔
344
    """The spatial coordinate corresponding to origin of a reference facet."""
345

346
    __slots__ = ()
1✔
347
    name = "x0f"
1✔
348

349
    @property
1✔
350
    def ufl_shape(self):
1✔
351
        """Get the UFL shape."""
352
        g = self._domain.geometric_dimension()
×
353
        return (g,)
×
354

355

356
@ufl_type()
1✔
357
class RidgeOrigin(GeometricRidgeQuantity):
1✔
358
    """The spatial coordinate corresponding to origin of a reference ridge."""
359

360
    __slots__ = ()
1✔
361
    name = "x0e"
1✔
362

363
    @property
1✔
364
    def ufl_shape(self):
1✔
365
        """Get the UFL shape."""
366
        g = self._domain.geometric_dimension()
×
367
        return (g,)
×
368

369

370
@ufl_type()
1✔
371
class CellFacetOrigin(GeometricFacetQuantity):
1✔
372
    """The reference cell coordinate corresponding to origin of a reference facet."""
373

374
    __slots__ = ()
1✔
375
    name = "X0f"
1✔
376

377
    @property
1✔
378
    def ufl_shape(self):
1✔
379
        """Get the UFL shape."""
380
        t = self._domain.topological_dimension()
×
381
        return (t,)
×
382

383

384
@ufl_type()
1✔
385
class CellRidgeOrigin(GeometricRidgeQuantity):
1✔
386
    """The reference cell coordinate corresponding to origin of a reference ridge."""
387

388
    __slots__ = ()
1✔
389
    name = "X0e"
1✔
390

391
    @property
1✔
392
    def ufl_shape(self):
1✔
393
        """Get the UFL shape."""
394
        t = self._domain.topological_dimension()
×
395
        return (t,)
×
396

397

398
# --- Jacobians of mappings between coordinate systems
399

400

401
@ufl_type()
1✔
402
class Jacobian(GeometricCellQuantity):
1✔
403
    r"""The Jacobian of the mapping from reference cell to spatial coordinates.
404

405
    .. math:: J_{ij} = \\frac{dx_i}{dX_j}
406
    """
407

408
    __slots__ = ()
1✔
409
    name = "J"
1✔
410

411
    @property
1✔
412
    def ufl_shape(self):
1✔
413
        """Return the number of coordinates defined (i.e. the geometric dimension of the domain)."""
414
        g = self._domain.geometric_dimension
1✔
415
        t = self._domain.topological_dimension
1✔
416
        return (g, t)
1✔
417

418
    def is_cellwise_constant(self):
1✔
419
        """Return whether this expression is spatially constant over each cell."""
420
        # Only true for a piecewise linear coordinate field in simplex cells
421
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
422

423

424
@ufl_type()
1✔
425
class FacetJacobian(GeometricFacetQuantity):
1✔
426
    """The Jacobian of the mapping from reference facet to spatial coordinates.
427

428
      FJ_ij = dx_i/dXf_j
429

430
    The FacetJacobian is the product of the Jacobian and CellFacetJacobian:
431

432
      FJ = dx/dXf = dx/dX dX/dXf = J * CFJ
433
    """
434

435
    __slots__ = ()
1✔
436
    name = "FJ"
1✔
437

438
    def __init__(self, domain):
1✔
439
        """Initialise."""
440
        GeometricFacetQuantity.__init__(self, domain)
1✔
441
        t = self._domain.topological_dimension
1✔
442
        if t < 2:
1✔
443
            raise ValueError("FacetJacobian is only defined for topological dimensions >= 2.")
×
444

445
    @property
1✔
446
    def ufl_shape(self):
1✔
447
        """Get the UFL shape."""
448
        g = self._domain.geometric_dimension
1✔
449
        t = self._domain.topological_dimension
1✔
450
        return (g, t - 1)
1✔
451

452
    def is_cellwise_constant(self):
1✔
453
        """Return whether this expression is spatially constant over each cell."""
454
        # Only true for a piecewise linear coordinate field in simplex
455
        # cells
456
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
457

458

459
@ufl_type()
1✔
460
class RidgeJacobian(GeometricRidgeQuantity):
1✔
461
    """The Jacobian of the mapping from reference ridge to spatial coordinates.
462

463
      RJ_ij = dx_i/dXe_j
464

465
    The RidgeJacobian is the product of the Jacobian and CellRidgeJacobian:
466

467
      RJ = dx/dXe = dx/dX dX/dXe = J * CRJ
468

469
    """
470

471
    __slots__ = ()
1✔
472
    name = "RJ"
1✔
473

474
    def __init__(self, domain):
1✔
475
        """Initialise."""
476
        GeometricRidgeQuantity.__init__(self, domain)
1✔
477
        t = self._domain.topological_dimension
1✔
478
        if t < 2:
1✔
479
            raise ValueError("RidgeJacobian is only defined for topological dimensions >= 2.")
×
480

481
    @property
1✔
482
    def ufl_shape(self):
1✔
483
        """Get the UFL shape."""
484
        g = self._domain.geometric_dimension
1✔
485
        t = self._domain.topological_dimension
1✔
486
        return (g, t - 2)
1✔
487

488
    def is_cellwise_constant(self):
1✔
489
        """Return whether this expression is spatially constant over each cell."""
490
        # Only true for a piecewise linear coordinate field in simplex
491
        # cells
492
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
493

494

495
@ufl_type()
1✔
496
class CellFacetJacobian(GeometricFacetQuantity):  # dX/dXf
1✔
497
    """The Jacobian of the mapping from reference facet to reference cell coordinates.
498

499
    CFJ_ij = dX_i/dXf_j
500
    """
501

502
    __slots__ = ()
1✔
503
    name = "CFJ"
1✔
504

505
    def __init__(self, domain):
1✔
506
        """Initialise."""
507
        GeometricFacetQuantity.__init__(self, domain)
×
NEW
508
        t = self._domain.topological_dimension
×
509
        if t < 2:
×
510
            raise ValueError("CellFacetJacobian is only defined for topological dimensions >= 2.")
×
511

512
    @property
1✔
513
    def ufl_shape(self):
1✔
514
        """Get the UFL shape."""
NEW
515
        t = self._domain.topological_dimension
×
516
        return (t, t - 1)
×
517

518
    def is_cellwise_constant(self):
1✔
519
        """Return whether this expression is spatially constant over each cell."""
520
        # This is always a constant mapping between two reference
521
        # coordinate systems.
522
        return True
×
523

524

525
@ufl_type()
1✔
526
class CellRidgeJacobian(GeometricRidgeQuantity):  # dX/dXe
1✔
527
    """The Jacobian of the mapping from reference ridge to reference cell coordinates.
528

529
    CRJ_ij = dX_i/dXe_j
530
    """
531

532
    __slots__ = ()
1✔
533
    name = "CRJ"
1✔
534

535
    def __init__(self, domain):
1✔
536
        """Initialise."""
537
        GeometricRidgeQuantity.__init__(self, domain)
×
NEW
538
        t = self._domain.topological_dimension
×
539
        if t < 2:
×
540
            raise ValueError("CellRidgeJacobian is only defined for topological dimensions >= 2.")
×
541

542
    @property
1✔
543
    def ufl_shape(self):
1✔
544
        """Get the UFL shape."""
NEW
545
        t = self._domain.topological_dimension
×
546
        return (t, t - 2)
×
547

548
    def is_cellwise_constant(self):
1✔
549
        """Return whether this expression is spatially constant over each cell."""
550
        # This is always a constant mapping between two reference
551
        # coordinate systems.
552
        return True
×
553

554

555
@ufl_type()
1✔
556
class FacetRidgeJacobian(GeometricRidgeQuantity):  # dXf/dXe
1✔
557
    """The Jacobian of the mapping from reference ridge to reference facet coordinates.
558

559
    FRJ_ij = dXf_i/dXe_j
560
    """
561

562
    __slots__ = ()
1✔
563
    name = "FRJ"
1✔
564

565
    def __init__(self, domain):
1✔
566
        """Initialise."""
567
        GeometricRidgeQuantity.__init__(self, domain)
×
NEW
568
        t = self._domain.topological_dimension
×
569
        if t < 2:
×
570
            raise ValueError("FacetRidgeJacobian is only defined for topological dimensions >= 2.")
×
571

572
    @property
1✔
573
    def ufl_shape(self):
1✔
574
        """Get the UFL shape."""
NEW
575
        t = self._domain.topological_dimension
×
576
        return (t - 1, t - 2)
×
577

578
    def is_cellwise_constant(self):
1✔
579
        """Return whether this expression is spatially constant over each cell."""
580
        # This is always a constant mapping between two reference
581
        # coordinate systems.
582
        return True
×
583

584

585
@ufl_type()
1✔
586
class ReferenceCellEdgeVectors(GeometricCellQuantity):
1✔
587
    """The vectors between reference cell vertices for each edge in cell."""
588

589
    __slots__ = ()
1✔
590
    name = "RCEV"
1✔
591

592
    def __init__(self, domain):
1✔
593
        """Initialise."""
594
        GeometricCellQuantity.__init__(self, domain)
×
NEW
595
        t = self._domain.topological_dimension
×
596
        if t < 2:
×
597
            raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.")
×
598

599
    @property
1✔
600
    def ufl_shape(self):
1✔
601
        """Get the UFL shape."""
602
        cell = extract_unique_domain(self).ufl_cell()
×
NEW
603
        ne = cell.num_edges
×
NEW
604
        t = cell.topological_dimension
×
UNCOV
605
        return (ne, t)
×
606

607
    def is_cellwise_constant(self):
1✔
608
        """Return whether this expression is spatially constant over each cell."""
609
        # This is always constant for a given cell type
610
        return True
×
611

612

613
@ufl_type()
1✔
614
class ReferenceFacetEdgeVectors(GeometricFacetQuantity):
1✔
615
    """The vectors between reference cell vertices for each edge in current facet."""
616

617
    __slots__ = ()
1✔
618
    name = "RFEV"
1✔
619

620
    def __init__(self, domain):
1✔
621
        """Initialise."""
622
        GeometricFacetQuantity.__init__(self, domain)
×
NEW
623
        t = self._domain.topological_dimension
×
624
        if t < 3:
×
625
            raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.")
×
626

627
    @property
1✔
628
    def ufl_shape(self):
1✔
629
        """Get the UFL shape."""
630
        cell = extract_unique_domain(self).ufl_cell()
×
NEW
631
        facet_types = cell.facet_types
×
632

633
        # Raise exception for cells with more than one facet type e.g. prisms
634
        if len(facet_types) > 1:
×
635
            raise Exception(f"Cell type {cell} not supported.")
×
636

NEW
637
        nfe = facet_types[0].num_edges
×
NEW
638
        t = cell.topological_dimension
×
UNCOV
639
        return (nfe, t)
×
640

641
    def is_cellwise_constant(self):
1✔
642
        """Return whether this expression is spatially constant over each cell."""
643
        # This is always constant for a given cell type
644
        return True
×
645

646

647
@ufl_type()
1✔
648
class CellVertices(GeometricCellQuantity):
1✔
649
    """Physical cell vertices."""
650

651
    __slots__ = ()
1✔
652
    name = "CV"
1✔
653

654
    def __init__(self, domain):
1✔
655
        """Initialise."""
656
        GeometricCellQuantity.__init__(self, domain)
×
657

658
    @property
1✔
659
    def ufl_shape(self):
1✔
660
        """Get the UFL shape."""
661
        domain = extract_unique_domain(self)
×
662
        cell = domain.ufl_cell()
×
NEW
663
        nv = cell.num_vertices
×
NEW
664
        g = domain.geometric_dimension
×
UNCOV
665
        return (nv, g)
×
666

667
    def is_cellwise_constant(self):
1✔
668
        """Return whether this expression is spatially constant over each cell."""
669
        # This is always constant for a given cell type
670
        return True
×
671

672

673
@ufl_type()
1✔
674
class CellEdgeVectors(GeometricCellQuantity):
1✔
675
    """The vectors between physical cell vertices for each edge in cell."""
676

677
    __slots__ = ()
1✔
678
    name = "CEV"
1✔
679

680
    def __init__(self, domain):
1✔
681
        """Initialise."""
682
        GeometricCellQuantity.__init__(self, domain)
×
NEW
683
        t = self._domain.topological_dimension
×
684
        if t < 2:
×
685
            raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.")
×
686

687
    @property
1✔
688
    def ufl_shape(self):
1✔
689
        """Get the UFL shape."""
690
        domain = extract_unique_domain(self)
×
691
        cell = domain.ufl_cell()
×
NEW
692
        ne = cell.num_edges
×
NEW
693
        g = domain.geometric_dimension
×
UNCOV
694
        return (ne, g)
×
695

696
    def is_cellwise_constant(self):
1✔
697
        """Return whether this expression is spatially constant over each cell."""
698
        # This is always constant for a given cell type
699
        return True
×
700

701

702
@ufl_type()
1✔
703
class FacetEdgeVectors(GeometricFacetQuantity):
1✔
704
    """The vectors between physical cell vertices for each edge in current facet."""
705

706
    __slots__ = ()
1✔
707
    name = "FEV"
1✔
708

709
    def __init__(self, domain):
1✔
710
        """Initialise."""
711
        GeometricFacetQuantity.__init__(self, domain)
×
NEW
712
        t = self._domain.topological_dimension
×
713
        if t < 3:
×
714
            raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.")
×
715

716
    @property
1✔
717
    def ufl_shape(self):
1✔
718
        """Get the UFL shape."""
719
        domain = extract_unique_domain(self)
×
720
        cell = domain.ufl_cell()
×
NEW
721
        facet_types = cell.facet_types
×
722

723
        # Raise exception for cells with more than one facet type e.g. prisms
724
        if len(facet_types) > 1:
×
725
            raise Exception(f"Cell type {cell} not supported.")
×
726

NEW
727
        nfe = facet_types[0].num_edges
×
NEW
728
        g = domain.geometric_dimension
×
UNCOV
729
        return (nfe, g)
×
730

731
    def is_cellwise_constant(self):
1✔
732
        """Return whether this expression is spatially constant over each cell."""
733
        # This is always constant for a given cell type
734
        return True
×
735

736

737
# --- Determinants (signed or pseudo) of geometry mapping Jacobians
738

739

740
@ufl_type()
1✔
741
class JacobianDeterminant(GeometricCellQuantity):
1✔
742
    """The determinant of the Jacobian.
743

744
    Represents the signed determinant of a square Jacobian or the
745
    pseudo-determinant of a non-square Jacobian.
746
    """
747

748
    __slots__ = ()
1✔
749
    name = "detJ"
1✔
750

751
    def is_cellwise_constant(self):
1✔
752
        """Return whether this expression is spatially constant over each cell."""
753
        # Only true for a piecewise linear coordinate field in simplex
754
        # cells
755
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
756

757

758
@ufl_type()
1✔
759
class FacetJacobianDeterminant(GeometricFacetQuantity):
1✔
760
    """The pseudo-determinant of the FacetJacobian."""
761

762
    __slots__ = ()
1✔
763
    name = "detFJ"
1✔
764

765
    def is_cellwise_constant(self):
1✔
766
        """Return whether this expression is spatially constant over each cell."""
767
        # Only true for a piecewise linear coordinate field in simplex cells
768
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
769

770

771
@ufl_type()
1✔
772
class RidgeJacobianDeterminant(GeometricRidgeQuantity):
1✔
773
    """The pseudo-determinant of the RidgeJacobian."""
774

775
    __slots__ = ()
1✔
776
    name = "detRJ"
1✔
777

778
    def is_cellwise_constant(self):
1✔
779
        """Return whether this expression is spatially constant over each cell."""
780
        # Only true for a piecewise linear coordinate field in simplex cells
781
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
782

783

784
@ufl_type()
1✔
785
class CellFacetJacobianDeterminant(GeometricFacetQuantity):
1✔
786
    """The pseudo-determinant of the CellFacetJacobian."""
787

788
    __slots__ = ()
1✔
789
    name = "detCFJ"
1✔
790

791
    def is_cellwise_constant(self):
1✔
792
        """Return whether this expression is spatially constant over each cell."""
793
        # Only true for a piecewise linear coordinate field in simplex
794
        # cells
795
        return self._domain.is_piecewise_linear_simplex_domain()
×
796

797

798
@ufl_type()
1✔
799
class CellRidgeJacobianDeterminant(GeometricRidgeQuantity):
1✔
800
    """The pseudo-determinant of the CellRidgeJacobian."""
801

802
    __slots__ = ()
1✔
803
    name = "detCRJ"
1✔
804

805
    def is_cellwise_constant(self):
1✔
806
        """Return whether this expression is spatially constant over each cell."""
807
        # Only true for a piecewise linear coordinate field in simplex
808
        # cells
809
        return self._domain.is_piecewise_linear_simplex_domain()
×
810

811

812
# --- Inverses (signed or pseudo) of geometry mapping Jacobians
813

814

815
@ufl_type()
1✔
816
class JacobianInverse(GeometricCellQuantity):
1✔
817
    """The inverse of the Jacobian.
818

819
    Represents the inverse of a square Jacobian or the pseudo-inverse of
820
    a non-square Jacobian.
821
    """
822

823
    __slots__ = ()
1✔
824
    name = "K"
1✔
825

826
    @property
1✔
827
    def ufl_shape(self):
1✔
828
        """Return the number of coordinates defined (i.e. the geometric dimension of the domain)."""
829
        g = self._domain.geometric_dimension
1✔
830
        t = self._domain.topological_dimension
1✔
831
        return (t, g)
1✔
832

833
    def is_cellwise_constant(self):
1✔
834
        """Return whether this expression is spatially constant over each cell."""
835
        # Only true for a piecewise linear coordinate field in simplex
836
        # cells
837
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
838

839

840
@ufl_type()
1✔
841
class FacetJacobianInverse(GeometricFacetQuantity):
1✔
842
    """The pseudo-inverse of the FacetJacobian."""
843

844
    __slots__ = ()
1✔
845
    name = "FK"
1✔
846

847
    def __init__(self, domain):
1✔
848
        """Initialise."""
849
        GeometricFacetQuantity.__init__(self, domain)
1✔
850
        t = self._domain.topological_dimension
1✔
851
        if t < 2:
1✔
852
            raise ValueError(
×
853
                "FacetJacobianInverse is only defined for topological dimensions >= 2."
854
            )
855

856
    @property
1✔
857
    def ufl_shape(self):
1✔
858
        """Get the UFL shape."""
859
        g = self._domain.geometric_dimension
1✔
860
        t = self._domain.topological_dimension
1✔
861
        return (t - 1, g)
1✔
862

863
    def is_cellwise_constant(self):
1✔
864
        """Return whether this expression is spatially constant over each cell."""
865
        # Only true for a piecewise linear coordinate field in simplex
866
        # cells
867
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
868

869

870
@ufl_type()
1✔
871
class RidgeJacobianInverse(GeometricRidgeQuantity):
1✔
872
    """The pseudo-inverse of the RidgeJacobian."""
873

874
    __slots__ = ()
1✔
875
    name = "EK"
1✔
876

877
    def __init__(self, domain):
1✔
878
        """Initialise."""
879
        GeometricRidgeQuantity.__init__(self, domain)
1✔
880
        t = self._domain.topological_dimension
1✔
881
        if t < 2:
1✔
882
            raise ValueError(
×
883
                "RidgeJacobianInverse is only defined for topological dimensions >= 2."
884
            )
885

886
    @property
1✔
887
    def ufl_shape(self):
1✔
888
        """Get the UFL shape."""
889
        g = self._domain.geometric_dimension
1✔
890
        t = self._domain.topological_dimension
1✔
891
        return (t - 2, g)
1✔
892

893
    def is_cellwise_constant(self):
1✔
894
        """Return whether this expression is spatially constant over each cell."""
895
        # Only true for a piecewise linear coordinate field in simplex
896
        # cells
897
        return self._domain.is_piecewise_linear_simplex_domain()
1✔
898

899

900
@ufl_type()
1✔
901
class CellFacetJacobianInverse(GeometricFacetQuantity):
1✔
902
    """The pseudo-inverse of the CellFacetJacobian."""
903

904
    __slots__ = ()
1✔
905
    name = "CFK"
1✔
906

907
    def __init__(self, domain):
1✔
908
        """Initialise."""
909
        GeometricFacetQuantity.__init__(self, domain)
×
910
        t = self._domain.topological_dimension()
×
911
        if t < 2:
×
912
            raise ValueError(
×
913
                "CellFacetJacobianInverse is only defined for topological dimensions >= 2."
914
            )
915

916
    @property
1✔
917
    def ufl_shape(self):
1✔
918
        """Get the UFL shape."""
919
        t = self._domain.topological_dimension()
×
920
        return (t - 1, t)
×
921

922
    def is_cellwise_constant(self):
1✔
923
        """Return whether this expression is spatially constant over each cell."""
924
        # Only true for a piecewise linear coordinate field in simplex cells
925
        return self._domain.is_piecewise_linear_simplex_domain()
×
926

927

928
@ufl_type()
1✔
929
class CellRidgeJacobianInverse(GeometricRidgeQuantity):
1✔
930
    """The pseudo-inverse of the RidgeFacetJacobian."""
931

932
    __slots__ = ()
1✔
933
    name = "CEK"
1✔
934

935
    def __init__(self, domain):
1✔
936
        """Initialise."""
937
        GeometricRidgeQuantity.__init__(self, domain)
×
938
        t = self._domain.topological_dimension()
×
939
        if t < 2:
×
940
            raise ValueError(
×
941
                "CellRidgeJacobianInverse is only defined for topological dimensions >= 2."
942
            )
943

944
    @property
1✔
945
    def ufl_shape(self):
1✔
946
        """Get the UFL shape."""
947
        t = self._domain.topological_dimension()
×
948
        return (t - 2, t)
×
949

950
    def is_cellwise_constant(self):
1✔
951
        """Return whether this expression is spatially constant over each cell."""
952
        # Only true for a piecewise linear coordinate field in simplex cells
953
        return self._domain.is_piecewise_linear_simplex_domain()
×
954

955

956
# --- Types representing normal or tangent vectors
957

958

959
@ufl_type()
1✔
960
class FacetNormal(GeometricFacetQuantity):
1✔
961
    """The outwards pointing normal vector of the current facet."""
962

963
    __slots__ = ()
1✔
964
    name = "n"
1✔
965

966
    @property
1✔
967
    def ufl_shape(self):
1✔
968
        """Return the number of coordinates defined (i.e. the geometric dimension of the domain)."""
969
        g = self._domain.geometric_dimension
1✔
970
        return (g,)
1✔
971

972
    def is_cellwise_constant(self):
1✔
973
        """Return whether this expression is spatially constant over each cell."""
974
        # For product cells, this is only true for some but not all
975
        # facets. Seems like too much work to fix right now.  Only
976
        # true for a piecewise linear coordinate field with simplex
977
        # _facets_.
978
        ce = self._domain.ufl_coordinate_element()
1✔
979
        is_piecewise_linear = ce.embedded_superdegree <= 1 and ce in H1
1✔
980
        return is_piecewise_linear and self._domain.ufl_cell().has_simplex_facets
1✔
981

982

983
@ufl_type()
1✔
984
class CellNormal(GeometricCellQuantity):
1✔
985
    """The upwards pointing normal vector of the current manifold cell."""
986

987
    __slots__ = ()
1✔
988
    name = "cell_normal"
1✔
989

990
    @property
1✔
991
    def ufl_shape(self):
1✔
992
        """Return the number of coordinates defined (i.e. the geometric dimension of the domain)."""
993
        g = self._domain.geometric_dimension()
×
994
        # t = self._domain.topological_dimension()
995
        # return (g-t,g) # TODO: Should it be CellNormals? For interval in 3D we have two!
996
        return (g,)
×
997

998
    def is_cellwise_constant(self):
1✔
999
        """Return whether this expression is spatially constant over each cell."""
1000
        # Only true for a piecewise linear coordinate field in simplex cells
1001
        return self._domain.is_piecewise_linear_simplex_domain()
×
1002

1003

1004
@ufl_type()
1✔
1005
class ReferenceNormal(GeometricFacetQuantity):
1✔
1006
    """The outwards pointing normal vector of the current facet on the reference cell."""
1007

1008
    __slots__ = ()
1✔
1009
    name = "reference_normal"
1✔
1010

1011
    @property
1✔
1012
    def ufl_shape(self):
1✔
1013
        """Get the UFL shape."""
NEW
1014
        t = self._domain.topological_dimension
×
1015
        return (t,)
×
1016

1017

1018
# --- Types representing measures of the cell and entities of the cell,
1019
# typically used for stabilisation terms
1020

1021
# TODO: Clean up this set of types? Document!
1022

1023

1024
@ufl_type()
1✔
1025
class ReferenceCellVolume(GeometricCellQuantity):
1✔
1026
    """The volume of the reference cell."""
1027

1028
    __slots__ = ()
1✔
1029
    name = "reference_cell_volume"
1✔
1030

1031

1032
@ufl_type()
1✔
1033
class ReferenceFacetVolume(GeometricFacetQuantity):
1✔
1034
    """The volume of the reference cell of the current facet."""
1035

1036
    __slots__ = ()
1✔
1037
    name = "reference_facet_volume"
1✔
1038

1039

1040
@ufl_type()
1✔
1041
class ReferenceRidgeVolume(GeometricRidgeQuantity):
1✔
1042
    """The volume of the reference cell of the current ridge."""
1043

1044
    __slots__ = ()
1✔
1045
    name = "reference_ridge_volume"
1✔
1046

1047

1048
@ufl_type()
1✔
1049
class CellVolume(GeometricCellQuantity):
1✔
1050
    """The volume of the cell."""
1051

1052
    __slots__ = ()
1✔
1053
    name = "volume"
1✔
1054

1055

1056
@ufl_type()
1✔
1057
class Circumradius(GeometricCellQuantity):
1✔
1058
    """The circumradius of the cell."""
1059

1060
    __slots__ = ()
1✔
1061
    name = "circumradius"
1✔
1062

1063

1064
@ufl_type()
1✔
1065
class CellDiameter(GeometricCellQuantity):
1✔
1066
    """The diameter of the cell, i.e., maximal distance of two points in the cell."""
1067

1068
    __slots__ = ()
1✔
1069
    name = "diameter"
1✔
1070

1071

1072
@ufl_type()
1✔
1073
# FIXME: Should this be allowed for interval domain?
1074
class FacetArea(GeometricFacetQuantity):
1✔
1075
    """The area of the facet."""
1076

1077
    __slots__ = ()
1✔
1078
    name = "facetarea"
1✔
1079

1080

1081
@ufl_type()
1✔
1082
class MinCellEdgeLength(GeometricCellQuantity):
1✔
1083
    """The minimum edge length of the cell."""
1084

1085
    __slots__ = ()
1✔
1086
    name = "mincelledgelength"
1✔
1087

1088

1089
@ufl_type()
1✔
1090
class MaxCellEdgeLength(GeometricCellQuantity):
1✔
1091
    """The maximum edge length of the cell."""
1092

1093
    __slots__ = ()
1✔
1094
    name = "maxcelledgelength"
1✔
1095

1096

1097
@ufl_type()
1✔
1098
class MinFacetEdgeLength(GeometricFacetQuantity):
1✔
1099
    """The minimum edge length of the facet."""
1100

1101
    __slots__ = ()
1✔
1102
    name = "minfacetedgelength"
1✔
1103

1104

1105
@ufl_type()
1✔
1106
class MaxFacetEdgeLength(GeometricFacetQuantity):
1✔
1107
    """The maximum edge length of the facet."""
1108

1109
    __slots__ = ()
1✔
1110
    name = "maxfacetedgelength"
1✔
1111

1112

1113
# --- Types representing other stuff
1114

1115

1116
@ufl_type()
1✔
1117
class CellOrientation(GeometricCellQuantity):
1✔
1118
    """The orientation (+1/-1) of the current cell.
1119

1120
    For non-manifold cells (tdim == gdim), this equals the sign
1121
    of the Jacobian determinant, i.e. +1 if the physical cell is
1122
    oriented the same way as the reference cell and -1 otherwise.
1123

1124
    For manifold cells of tdim==gdim-1 this is input data belonging
1125
    to the mesh, used to distinguish between the sides of the manifold.
1126
    """
1127

1128
    __slots__ = ()
1✔
1129
    name = "cell_orientation"
1✔
1130

1131

1132
@ufl_type()
1✔
1133
class FacetOrientation(GeometricFacetQuantity):
1✔
1134
    """The orientation (+1/-1) of the current facet relative to the reference cell."""
1135

1136
    __slots__ = ()
1✔
1137
    name = "facet_orientation"
1✔
1138

1139

1140
# This doesn't quite fit anywhere. Make a special set of symbolic
1141
# terminal types instead?
1142
@ufl_type()
1✔
1143
class QuadratureWeight(GeometricQuantity):
1✔
1144
    """The current quadrature weight.
1145

1146
    Only used inside a quadrature context.
1147
    """
1148

1149
    __slots__ = ()
1✔
1150
    name = "weight"
1✔
1151

1152
    def is_cellwise_constant(self):
1✔
1153
        """Return whether this expression is spatially constant over each cell."""
1154
        # The weight usually varies with the quadrature points
1155
        return False
×
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