Skip to content

Commit 2b5567f

Browse files
authored
Merge pull request #1011 from jhdark/alt_coordinate_support
Cylindrical and Spherical coordinate support
2 parents e3dcd34 + 1eaaca6 commit 2b5567f

10 files changed

Lines changed: 528 additions & 33 deletions

src/festim/exports/surface_flux.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,42 @@
33
from scifem import assemble_scalar
44

55
from festim.exports.surface_quantity import SurfaceQuantity
6+
from festim.species import Species
7+
from festim.subdomain.surface_subdomain import SurfaceSubdomain
68

79

810
class SurfaceFlux(SurfaceQuantity):
911
"""Computes the flux of a field on a given surface
1012
1113
Args:
12-
field (festim.Species): species for which the surface flux is computed
13-
surface (festim.SurfaceSubdomain1D): surface subdomain
14-
filename (str, optional): name of the file to which the surface flux is exported
14+
field: species for which the surface flux is computed
15+
surface: surface subdomain
16+
filename: name of the file to which the surface flux is exported
1517
1618
Attributes:
1719
see `festim.SurfaceQuantity`
1820
"""
1921

22+
field: Species
23+
surface: SurfaceSubdomain
24+
filename: str
25+
26+
title: str
27+
value: float
28+
data: list[float]
29+
2030
@property
2131
def title(self):
2232
return f"{self.field.name} flux surface {self.surface.id}"
2333

24-
def compute(self, u, ds: ufl.Measure, entity_maps=None):
34+
def __init__(
35+
self, field: Species, surface: SurfaceSubdomain, filename: str | None = None
36+
) -> None:
37+
super().__init__(field=field, surface=surface, filename=filename)
38+
39+
def compute(
40+
self, u: fem.Function | ufl.indexed.Indexed, ds: ufl.Measure, entity_maps=None
41+
):
2542
"""Computes the value of the flux at the surface
2643
2744
Args:

src/festim/exports/surface_quantity.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,28 @@ class SurfaceQuantity:
88
"""Export SurfaceQuantity
99
1010
Args:
11-
field (festim.Species): species for which the surface flux is computed
12-
surface (festim.SurfaceSubdomain1D): surface subdomain
13-
filename (str, optional): name of the file to which the surface flux is exported
11+
field: species for which the surface flux is computed
12+
surface: surface subdomain
13+
filename: name of the file to which the surface flux is exported
1414
1515
Attributes:
16-
field (festim.Species): species for which the surface flux is computed
17-
surface (festim.SurfaceSubdomain): surface subdomain
18-
filename (str): name of the file to which the surface flux is exported
19-
t (list): list of time values
20-
data (list): list of values of the surface quantity
16+
field: species for which the surface flux is computed
17+
surface: surface subdomain
18+
filename: name of the file to which the surface flux is exported
19+
t: list of time values
20+
data: list of values of the surface quantity
2121
"""
2222

23-
def __init__(self, field, surface, filename: str | None = None) -> None:
23+
field: Species
24+
surface: SurfaceSubdomain
25+
filename: str | None
26+
27+
t: list[float]
28+
data: list[float]
29+
30+
def __init__(
31+
self, field: Species, surface: SurfaceSubdomain, filename: str | None = None
32+
) -> None:
2433
self.field = field
2534
self.surface = surface
2635
self.filename = filename

src/festim/exports/volume_quantity.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
import csv
2-
import os
32

4-
from festim import species
3+
from festim.species import Species
4+
from festim.subdomain.volume_subdomain import VolumeSubdomain
55

66

77
class VolumeQuantity:
88
"""Export VolumeQuantity
99
1010
Args:
11-
field (festim.Species): species for which the volume quantity is computed
12-
volume (festim.VolumeSubdomain): volume subdomain
13-
filename (str, optional): name of the file to which the volume quantity is exported
11+
field: species for which the volume quantity is computed
12+
volume: volume subdomain
13+
filename: name of the file to which the volume quantity is exported
1414
1515
Attributes:
16-
field (festim.Species): species for which the volume quantity is computed
17-
volume (festim.VolumeSubdomain): volume subdomain
18-
filename (str): name of the file to which the volume quantity is exported
19-
t (list): list of time values
20-
data (list): list of values of the volume quantity
16+
field: species for which the volume quantity is computed
17+
volume: volume subdomain
18+
filename: name of the file to which the volume quantity is exported
19+
t: list of time values
20+
data: list of values of the volume quantity
2121
"""
2222

23-
def __init__(self, field, volume, filename: str = None) -> None:
23+
field: Species
24+
volume: VolumeSubdomain
25+
filename: str | None
26+
27+
t: list[float]
28+
data: list[float]
29+
30+
def __init__(self, field, volume, filename: str | None = None) -> None:
2431
self.field = field
2532
self.volume = volume
2633
self.filename = filename
@@ -50,7 +57,7 @@ def field(self):
5057
@field.setter
5158
def field(self, value):
5259
# check that field is festim.Species
53-
if not isinstance(value, (species.Species, str)):
60+
if not isinstance(value, Species | str):
5461
raise TypeError("field must be of type festim.Species")
5562

5663
self._field = value

src/festim/hydrogen_transport_problem.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ def initialise_exports(self):
421421
else:
422422
adios4dolfinx.write_mesh(export.filename, mesh=self.mesh.mesh)
423423

424+
elif isinstance(export, exports.SurfaceQuantity | exports.VolumeQuantity):
425+
# raise not implemented error if the derived quantity don't match the
426+
# type of mesh eg. SurfaceFlux is used with cylindrical mesh
427+
if self.mesh.coordinate_system != "cartesian":
428+
raise NotImplementedError(
429+
f"Derived quantity exports are not implemented for "
430+
f"{self.mesh.coordinate_system} meshes"
431+
)
432+
424433
# if name of species is given then replace with species object
425434
if isinstance(export.field, list):
426435
for idx, field in enumerate(export.field):
@@ -784,9 +793,24 @@ def create_formulation(self):
784793
self.mesh.mesh, self.temperature_fenics, spe
785794
)
786795
if spe.mobile:
787-
self.formulation += ufl.dot(D * ufl.grad(u), ufl.grad(v)) * self.dx(
788-
vol.id
789-
)
796+
if self.mesh.coordinate_system == "cartesian":
797+
self.formulation += ufl.dot(
798+
D * ufl.grad(u), ufl.grad(v)
799+
) * self.dx(vol.id)
800+
elif self.mesh.coordinate_system == "cylindrical":
801+
r = ufl.SpatialCoordinate(self.mesh.mesh)[0]
802+
self.formulation += (
803+
r
804+
* ufl.dot(D * ufl.grad(u), ufl.grad(v / r))
805+
* self.dx(vol.id)
806+
)
807+
elif self.mesh.coordinate_system == "spherical":
808+
r = ufl.SpatialCoordinate(self.mesh.mesh)[0]
809+
self.formulation += (
810+
r**2
811+
* ufl.dot(D * ufl.grad(u), ufl.grad(v / r**2))
812+
* self.dx(vol.id)
813+
)
790814

791815
if self.settings.transient:
792816
self.formulation += ((u - u_n) / self.dt) * v * self.dx(vol.id)
@@ -1319,7 +1343,24 @@ def create_subdomain_formulation(self, subdomain: _subdomain.VolumeSubdomain):
13191343
form += ((u - u_n) / self.dt) * v * self.dx(subdomain.id)
13201344

13211345
if spe.mobile:
1322-
form += ufl.inner(D * ufl.grad(u), ufl.grad(v)) * self.dx(subdomain.id)
1346+
if self.mesh.coordinate_system == "cartesian":
1347+
form += ufl.dot(D * ufl.grad(u), ufl.grad(v)) * self.dx(
1348+
subdomain.id
1349+
)
1350+
elif self.mesh.coordinate_system == "cylindrical":
1351+
r = ufl.SpatialCoordinate(self.mesh.mesh)[0]
1352+
form += (
1353+
r
1354+
* ufl.dot(D * ufl.grad(u), ufl.grad(v / r))
1355+
* self.dx(subdomain.id)
1356+
)
1357+
elif self.mesh.coordinate_system == "spherical":
1358+
r = ufl.SpatialCoordinate(self.mesh.mesh)[0]
1359+
form += (
1360+
r**2
1361+
* ufl.dot(D * ufl.grad(u), ufl.grad(v / r**2))
1362+
* self.dx(subdomain.id)
1363+
)
13231364

13241365
# add reaction terms
13251366
for reaction in self.reactions:

src/festim/mesh/mesh.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ class Mesh:
1010
Mesh class
1111
1212
Args:
13-
mesh The mesh. Defaults to None.
13+
mesh: The mesh. Defaults to None.
14+
coordinate_system: the coordinate system of the mesh ("cartesian",
15+
"cylindrical", "spherical"). Defaults to "cartesian".
1416
1517
Attributes:
1618
mesh The mesh
@@ -20,9 +22,18 @@ class Mesh:
2022
of the mesh.
2123
"""
2224

23-
_mesh: dolfinx.mesh.Mesh
25+
mesh: dolfinx.mesh.Mesh
26+
coordinate_system: str | None
2427

25-
def __init__(self, mesh: dolfinx_Mesh | None = None):
28+
vdim: int
29+
fdim: int
30+
n: ufl.FacetNormal
31+
32+
def __init__(
33+
self,
34+
mesh: dolfinx_Mesh | None = None,
35+
coordinate_system: str | None = "cartesian",
36+
):
2637
self.mesh = mesh
2738
if self._mesh is not None:
2839
# create cell to facet connectivity
@@ -34,6 +45,9 @@ def __init__(self, mesh: dolfinx_Mesh | None = None):
3445
self._mesh.topology.create_connectivity(
3546
self._mesh.topology.dim - 1, self._mesh.topology.dim
3647
)
48+
self.coordinate_system = coordinate_system
49+
50+
self.check_mesh_dim_coords()
3751

3852
@property
3953
def mesh(self):
@@ -46,6 +60,20 @@ def mesh(self, value):
4660
else:
4761
raise TypeError("Mesh must be of type dolfinx.mesh.Mesh")
4862

63+
@property
64+
def coordinate_system(self):
65+
return self._coordinate_system
66+
67+
@coordinate_system.setter
68+
def coordinate_system(self, value):
69+
if value in ["cartesian", "cylindrical", "spherical"]:
70+
self._coordinate_system = value
71+
else:
72+
raise ValueError(
73+
"coordinate_system must be one of 'cartesian', "
74+
"'cylindrical', or 'spherical'"
75+
)
76+
4977
@property
5078
def vdim(self):
5179
if self._mesh is None:
@@ -144,3 +172,16 @@ def define_meshtags(self, surface_subdomains, volume_subdomains, interfaces=None
144172
)
145173

146174
return facet_meshtags, volume_meshtags
175+
176+
def check_mesh_dim_coords(self):
177+
"""Checks if the used coordinates can be applied for geometry with the specified
178+
dimensions"""
179+
180+
if self.coordinate_system == "spherical" and self.vdim != 1:
181+
raise AttributeError(
182+
"spherical coordinates can be used for one-dimensional domains only"
183+
)
184+
if self.coordinate_system == "cylindrical" and self.vdim == 3:
185+
raise AttributeError(
186+
"cylindrical coordinates cannot be used for 3D domains"
187+
)

test/benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import tempfile
12
import time
23

34
from mpi4py import MPI
@@ -32,7 +33,6 @@
3233
exp,
3334
grad,
3435
)
35-
import tempfile
3636

3737

3838
def fenics_test_permeation_problem(mesh_size=1001):

0 commit comments

Comments
 (0)