Skip to content

Commit 678eb1a

Browse files
Merge pull request #939 from jhdark/cyl_and_sph_total_quantities
Total surface and volume derived quantities for cylindrical and spherical meshes
2 parents b494df6 + e3da07f commit 678eb1a

File tree

5 files changed

+728
-6
lines changed

5 files changed

+728
-6
lines changed

festim/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,16 @@
8484
from .exports.derived_quantities.minimum_volume import MinimumVolume
8585
from .exports.derived_quantities.minimum_surface import MinimumSurface
8686
from .exports.derived_quantities.maximum_surface import MaximumSurface
87-
from .exports.derived_quantities.total_surface import TotalSurface
87+
from .exports.derived_quantities.total_surface import (
88+
TotalSurface,
89+
TotalSurfaceCylindrical,
90+
TotalSurfaceSpherical,
91+
)
92+
from .exports.derived_quantities.total_volume import (
93+
TotalVolume,
94+
TotalVolumeCylindrical,
95+
TotalVolumeSpherical,
96+
)
8897
from .exports.derived_quantities.total_volume import TotalVolume
8998
from .exports.derived_quantities.average_surface import (
9099
AverageSurface,

festim/exports/derived_quantities/total_surface.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from festim import SurfaceQuantity
22
import fenics as f
3+
import numpy as np
34

45

56
class TotalSurface(SurfaceQuantity):
@@ -58,3 +59,166 @@ def title(self):
5859

5960
def compute(self):
6061
return f.assemble(self.function * self.ds(self.surface))
62+
63+
64+
class TotalSurfaceCylindrical(TotalSurface):
65+
"""
66+
Computes the total value of a field on a given surface
67+
int(f ds)
68+
ds is the surface measure in cylindrical coordinates.
69+
ds = r dr dtheta
70+
71+
Args:
72+
field (str, int): the field ("solute", 0, 1, "T", "retention")
73+
surface (int): the surface id
74+
azimuth_range (tuple, optional): Range of the azimuthal angle
75+
(theta) needs to be between 0 and 2 pi. Defaults to (0, 2 * np.pi)
76+
77+
Attributes:
78+
field (str, int): the field ("solute", 0, 1, "T", "retention")
79+
surface (int): the surface id
80+
title (str): the title of the derived quantity
81+
show_units (bool): show the units in the title in the derived quantities
82+
file
83+
function (dolfin.function.function.Function): the solution function of
84+
the field
85+
r (ufl.indexed.Indexed): the radius of the cylinder
86+
87+
.. note::
88+
Units are in H/m in 1D, H in 2D domains for hydrogen concentration
89+
and K m in 1D, K m2 in 2D domains for temperature
90+
"""
91+
92+
def __init__(self, field, surface, azimuth_range=(0, 2 * np.pi)) -> None:
93+
super().__init__(field=field, surface=surface)
94+
self.r = None
95+
self.azimuth_range = azimuth_range
96+
97+
@property
98+
def export_unit(self):
99+
# obtain domain dimension
100+
try:
101+
dim = self.function.function_space().mesh().topology().dim()
102+
except AttributeError:
103+
dim = self.dx._domain._topological_dimension
104+
# TODO we could simply do that all the time
105+
# return unit depending on field and dimension of domain
106+
if self.field == "T":
107+
return f"K m{dim}".replace(" m1", " m")
108+
else:
109+
return f"H m{dim-2}".replace(" m0", "")
110+
111+
@property
112+
def azimuth_range(self):
113+
return self._azimuth_range
114+
115+
@azimuth_range.setter
116+
def azimuth_range(self, value):
117+
if value[0] < 0 or value[1] > 2 * np.pi:
118+
raise ValueError("Azimuthal range must be between 0 and pi")
119+
self._azimuth_range = value
120+
121+
@property
122+
def allowed_meshes(self):
123+
return ["cylindrical"]
124+
125+
def compute(self):
126+
127+
if self.r is None:
128+
mesh = (
129+
self.function.function_space().mesh()
130+
) # get the mesh from the function
131+
rthetaz = f.SpatialCoordinate(mesh) # get the coordinates from the mesh
132+
self.r = rthetaz[0] # only care about r here
133+
134+
tot_surf = f.assemble(self.function * self.r * self.ds(self.surface))
135+
tot_surf *= self.azimuth_range[1] - self.azimuth_range[0]
136+
137+
return tot_surf
138+
139+
140+
class TotalSurfaceSpherical(TotalSurface):
141+
"""
142+
Computes the total value of a field on a given surface
143+
int(f ds)
144+
ds is the surface measure in spherical coordinates.
145+
ds = r**2 sin(theta) dtheta dphi
146+
147+
Args:
148+
field (str, int): the field ("solute", 0, 1, "T", "retention")
149+
surface (int): the surface id
150+
azimuth_range (tuple, optional): Range of the azimuthal angle
151+
(phi) needs to be between 0 and 2 pi. Defaults to (0, 2 * np.pi)
152+
polar_range (tuple, optional): Range of the polar angle
153+
(theta) needs to be between 0 and pi. Defaults to (0, np.pi).
154+
155+
Attributes:
156+
field (str, int): the field ("solute", 0, 1, "T", "retention")
157+
surface (int): the surface id
158+
title (str): the title of the derived quantity
159+
show_units (bool): show the units in the title in the derived quantities
160+
file
161+
function (dolfin.function.function.Function): the solution function of
162+
the field
163+
r (ufl.indexed.Indexed): the radius of the cylinder
164+
165+
.. note::
166+
Units are in H for hydrogen concentration
167+
and K in 1D, K m in 2D domains for temperature
168+
"""
169+
170+
def __init__(
171+
self, field, surface, azimuth_range=(0, 2 * np.pi), polar_range=(0, np.pi)
172+
) -> None:
173+
super().__init__(field=field, surface=surface)
174+
self.r = None
175+
self.azimuth_range = azimuth_range
176+
self.polar_range = polar_range
177+
178+
@property
179+
def export_unit(self):
180+
if self.field == "T":
181+
return f"K m2"
182+
else:
183+
return "H"
184+
185+
@property
186+
def azimuth_range(self):
187+
return self._azimuth_range
188+
189+
@azimuth_range.setter
190+
def azimuth_range(self, value):
191+
if value[0] < 0 or value[1] > 2 * np.pi:
192+
raise ValueError("Azimuthal range must be between 0 and 2 pi")
193+
self._azimuth_range = value
194+
195+
@property
196+
def polar_range(self):
197+
return self._polar_range
198+
199+
@polar_range.setter
200+
def polar_range(self, value):
201+
if value[0] < 0 or value[1] > np.pi:
202+
raise ValueError("Polar range must be between 0 and pi")
203+
self._polar_range = value
204+
205+
@property
206+
def allowed_meshes(self):
207+
return ["spherical"]
208+
209+
def compute(self):
210+
211+
if self.r is None:
212+
mesh = (
213+
self.function.function_space().mesh()
214+
) # get the mesh from the function
215+
rthetaphi = f.SpatialCoordinate(mesh) # get the coordinates from the mesh
216+
self.r = rthetaphi[0] # only care about r here
217+
218+
tot_surf = f.assemble(self.function * self.r**2 * self.ds(self.surface))
219+
220+
tot_surf *= (self.azimuth_range[1] - self.azimuth_range[0]) * (
221+
np.cos(self.polar_range[0]) - np.cos(self.polar_range[1])
222+
)
223+
224+
return tot_surf

festim/exports/derived_quantities/total_volume.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from festim import VolumeQuantity
22
import fenics as f
3+
import numpy as np
34

45

56
class TotalVolume(VolumeQuantity):
@@ -58,3 +59,163 @@ def title(self):
5859

5960
def compute(self):
6061
return f.assemble(self.function * self.dx(self.volume))
62+
63+
64+
class TotalVolumeCylindrical(TotalVolume):
65+
"""Computes the total value of a field for a given volume
66+
int(f dx)
67+
dx is the volume measure in cylindrical coordinates.
68+
dx = r dr dtheta dz
69+
70+
Args:
71+
field (str, int): the field ("solute", 0, 1, "T", "retention")
72+
volume (int): the volume id
73+
azimuth_range (tuple, optional): Range of the azimuthal angle
74+
(theta) needs to be between 0 and 2 pi. Defaults to (0, 2 * np.pi)
75+
76+
Attributes:
77+
field (str, int): the field ("solute", 0, 1, "T", "retention")
78+
volume (int): the volume id
79+
title (str): the title of the derived quantity
80+
show_units (bool): show the units in the title in the derived quantities
81+
file
82+
function (dolfin.function.function.Function): the solution function of
83+
the field
84+
r (ufl.indexed.Indexed): the radius of the cylinder
85+
86+
.. note::
87+
Units are in H/m in 1D and H in 2D for hydrogen concentration
88+
and K m2 in 1D, K m3 in 2D domains for temperature
89+
"""
90+
91+
def __init__(self, field, volume, azimuth_range=(0, 2 * np.pi)) -> None:
92+
super().__init__(field=field, volume=volume)
93+
self.r = None
94+
self.azimuth_range = azimuth_range
95+
96+
@property
97+
def export_unit(self):
98+
# obtain domain dimension
99+
try:
100+
dim = self.function.function_space().mesh().topology().dim()
101+
except AttributeError:
102+
dim = self.dx._domain._topological_dimension
103+
# TODO we could simply do that all the time
104+
# return unit depending on field and dimension of domain
105+
if self.field == "T":
106+
return f"K m{dim+1}"
107+
else:
108+
return f"H m{dim-2}".replace(" m0", "")
109+
110+
@property
111+
def azimuth_range(self):
112+
return self._azimuth_range
113+
114+
@azimuth_range.setter
115+
def azimuth_range(self, value):
116+
if value[0] < 0 or value[1] > 2 * np.pi:
117+
raise ValueError("Azimuthal range must be between 0 and 2 pi")
118+
self._azimuth_range = value
119+
120+
@property
121+
def allowed_meshes(self):
122+
return ["cylindrical"]
123+
124+
def compute(self):
125+
126+
if self.r is None:
127+
mesh = (
128+
self.function.function_space().mesh()
129+
) # get the mesh from the function
130+
rthetaz = f.SpatialCoordinate(mesh) # get the coordinates from the mesh
131+
self.r = rthetaz[0] # only care about r here
132+
133+
tot_vol = f.assemble(self.function * self.r * self.dx(self.volume))
134+
tot_vol *= self.azimuth_range[1] - self.azimuth_range[0]
135+
136+
return tot_vol
137+
138+
139+
class TotalVolumeSpherical(TotalVolume):
140+
"""Computes the total value of a field for a given volume
141+
int(f dx)
142+
dx is the volume measure in cylindrical coordinates.
143+
dx = r**2 sin(theta) dtheta dphi dr
144+
145+
Args:
146+
field (str, int): the field ("solute", 0, 1, "T", "retention")
147+
volume (int): the volume id
148+
azimuth_range (tuple, optional): Range of the azimuthal angle
149+
(phi) needs to be between 0 and 2 pi. Defaults to (0, 2 * np.pi)
150+
polar_range (tuple, optional): Range of the polar angle
151+
(theta) needs to be between 0 and pi. Defaults to (0, np.pi).
152+
153+
Attributes:
154+
field (str, int): the field ("solute", 0, 1, "T", "retention")
155+
volume (int): the volume id
156+
title (str): the title of the derived quantity
157+
show_units (bool): show the units in the title in the derived quantities
158+
file
159+
function (dolfin.function.function.Function): the solution function of
160+
the field
161+
r (ufl.indexed.Indexed): the radius of the cylinder
162+
163+
.. note::
164+
Units are in H for hydrogen concentration and K m2 for temperature
165+
"""
166+
167+
def __init__(
168+
self, field, volume, azimuth_range=(0, 2 * np.pi), polar_range=(0, np.pi)
169+
) -> None:
170+
super().__init__(field=field, volume=volume)
171+
self.r = None
172+
self.azimuth_range = azimuth_range
173+
self.polar_range = polar_range
174+
175+
@property
176+
def export_unit(self):
177+
if self.field == "T":
178+
return f"K m3"
179+
else:
180+
return f"H"
181+
182+
@property
183+
def azimuth_range(self):
184+
return self._azimuth_range
185+
186+
@azimuth_range.setter
187+
def azimuth_range(self, value):
188+
if value[0] < 0 or value[1] > 2 * np.pi:
189+
raise ValueError("Azimuthal range must be between 0 and pi")
190+
self._azimuth_range = value
191+
192+
@property
193+
def polar_range(self):
194+
return self._polar_range
195+
196+
@polar_range.setter
197+
def polar_range(self, value):
198+
if value[0] < 0 or value[1] > np.pi:
199+
raise ValueError("Polar range must be between 0 and pi")
200+
self._polar_range = value
201+
202+
@property
203+
def allowed_meshes(self):
204+
return ["spherical"]
205+
206+
def compute(self):
207+
208+
if self.r is None:
209+
mesh = (
210+
self.function.function_space().mesh()
211+
) # get the mesh from the function
212+
rthetaphi = f.SpatialCoordinate(mesh) # get the coordinates from the mesh
213+
self.r = rthetaphi[0] # only care about r here
214+
215+
tot_vol = f.assemble(self.function * self.r**2 * self.dx(self.volume))
216+
217+
tot_vol *= (self.azimuth_range[1] - self.azimuth_range[0]) * (
218+
np.cos(self.polar_range[0]) - np.cos(self.polar_range[1])
219+
)
220+
221+
return tot_vol

0 commit comments

Comments
 (0)