Skip to content

Commit ade86fa

Browse files
committed
Merge branch 'fenicsx' into milestones_from_exports
2 parents 792f4c3 + 61bd43d commit ade86fa

File tree

10 files changed

+198
-23
lines changed

10 files changed

+198
-23
lines changed

examples/multi_material_2d_bis.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def half(x):
129129

130130
my_model.temperature = lambda x: 400 + 10 * x[1] + 100 * x[0]
131131

132-
my_model.settings = F.Settings(atol=None, rtol=1e-5, transient=False)
132+
my_model.settings = F.Settings(atol=1e-10, rtol=1e-5, transient=False)
133133

134134
my_model.exports = [
135135
F.VTXSpeciesExport(f"c_{subdomain.id}.bp", field=H, subdomain=subdomain)
@@ -149,6 +149,7 @@ def half(x):
149149
entity_maps = {sd.submesh: sd.parent_to_submesh for sd in my_model.volume_subdomains}
150150
entity_maps[mesh] = bottom_domain.submesh_to_mesh
151151

152+
ds = ufl.Measure("ds", domain=mesh, subdomain_data=my_model.facet_meshtags)
152153
ds_b = ufl.Measure("ds", domain=bottom_domain.submesh, subdomain_data=bottom_domain.ft)
153154
ds_t = ufl.Measure("ds", domain=top_domain.submesh, subdomain_data=top_domain.ft)
154155
dx_b = ufl.Measure("dx", domain=bottom_domain.submesh)
@@ -191,3 +192,16 @@ def half(x):
191192
entity_maps={mesh: top_domain.submesh_to_mesh},
192193
)
193194
print(dolfinx.fem.assemble_scalar(form))
195+
196+
197+
# using submesh function
198+
u = H.subdomain_to_post_processing_solution[bottom_domain]
199+
form = dolfinx.fem.form(
200+
ufl.dot(
201+
D * ufl.grad(u),
202+
n_b,
203+
)
204+
* ds(bottom_surface.id),
205+
entity_maps={sd.submesh: sd.parent_to_submesh for sd in my_model.volume_subdomains},
206+
)
207+
print(dolfinx.fem.assemble_scalar(form))

src/festim/exports/average_volume.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ class AverageVolume(VolumeQuantity):
1919
def title(self):
2020
return f"Average {self.field.name} volume {self.volume.id}"
2121

22-
def compute(self, dx):
22+
def compute(self, u, dx, entity_maps=None):
2323
"""
2424
Computes the average value of solution function within the defined volume
2525
subdomain, and appends it to the data list
2626
"""
2727
self.value = fem.assemble_scalar(
28-
fem.form(self.field.solution * dx(self.volume.id))
28+
fem.form(u * dx(self.volume.id), entity_maps=entity_maps)
2929
) / fem.assemble_scalar(fem.form(1 * dx(self.volume.id)))
3030
self.data.append(self.value)

src/festim/exports/surface_flux.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,27 @@ class SurfaceFlux(SurfaceQuantity):
2020
def title(self):
2121
return f"{self.field.name} flux surface {self.surface.id}"
2222

23-
def compute(self, ds):
23+
def compute(self, u, ds: ufl.Measure, entity_maps=None):
2424
"""Computes the value of the flux at the surface
2525
2626
Args:
27-
ds (ufl.Measure): surface measure of the model
27+
u: field for which the flux is computed
28+
ds: surface measure of the model
29+
entity_maps: entity maps relating parent mesh and submesh
2830
"""
2931

3032
# obtain mesh normal from field
3133
# if case multispecies, solution is an index, use sub_function_space
32-
if isinstance(self.field.solution, ufl.indexed.Indexed):
34+
if isinstance(u, ufl.indexed.Indexed):
3335
mesh = self.field.sub_function_space.mesh
3436
else:
35-
mesh = self.field.solution.function_space.mesh
37+
mesh = u.function_space.mesh
3638
n = ufl.FacetNormal(mesh)
3739

3840
self.value = fem.assemble_scalar(
3941
fem.form(
40-
-self.D
41-
* ufl.dot(ufl.grad(self.field.solution), n)
42-
* ds(self.surface.id)
42+
-self.D * ufl.dot(ufl.grad(u), n) * ds(self.surface.id),
43+
entity_maps=entity_maps,
4344
)
4445
)
4546
self.data.append(self.value)

src/festim/exports/total_volume.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@ class TotalVolume(VolumeQuantity):
2020
def title(self):
2121
return f"Total {self.field.name} volume {self.volume.id}"
2222

23-
def compute(self, dx: ufl.Measure):
23+
def compute(self, u, dx: ufl.Measure, entity_maps=None):
2424
"""
2525
Computes the value of the total volume of the field in the volume subdomain
2626
and appends it to the data list
2727
2828
Args:
29-
dx (ufl.Measure): volume measure of the model
29+
u: field for which the total volume is computed
30+
dx: volume measure of the model
31+
entity_maps: entity maps relating parent mesh and submesh
3032
"""
3133
self.value = fem.assemble_scalar(
32-
fem.form(self.field.solution * dx(self.volume.id))
34+
fem.form(u * dx(self.volume.id), entity_maps=entity_maps)
3335
)
3436
self.data.append(self.value)

src/festim/hydrogen_transport_problem.py

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -947,9 +947,7 @@ def post_processing(self):
947947
"Advection terms are not currently accounted for in the "
948948
"evaluation of surface flux values"
949949
)
950-
export.compute(
951-
self.ds,
952-
)
950+
export.compute(export.field.solution, self.ds)
953951
else:
954952
export.compute()
955953
# update export data
@@ -960,7 +958,7 @@ def post_processing(self):
960958
export.write(t=float(self.t))
961959
elif isinstance(export, exports.VolumeQuantity):
962960
if isinstance(export, exports.TotalVolume | exports.AverageVolume):
963-
export.compute(self.dx)
961+
export.compute(u=export.field.solution, dx=self.dx)
964962
else:
965963
export.compute()
966964
# update export data
@@ -1064,6 +1062,21 @@ def initialise(self):
10641062

10651063
for subdomain in self.volume_subdomains:
10661064
self.define_function_spaces(subdomain)
1065+
# create global DG function spaces of degree 0 and 1
1066+
element_DG0 = basix.ufl.element(
1067+
"DG",
1068+
self.mesh.mesh.basix_cell(),
1069+
0,
1070+
basix.LagrangeVariant.equispaced,
1071+
)
1072+
element_DG1 = basix.ufl.element(
1073+
"DG",
1074+
self.mesh.mesh.basix_cell(),
1075+
1,
1076+
basix.LagrangeVariant.equispaced,
1077+
)
1078+
self.V_DG_0 = fem.functionspace(self.mesh.mesh, element_DG0)
1079+
self.V_DG_1 = fem.functionspace(self.mesh.mesh, element_DG1)
10671080

10681081
self.define_temperature()
10691082
self.convert_source_input_values_to_fenics_objects()
@@ -1503,8 +1516,32 @@ def initialise_exports(self):
15031516
f"Export type {type(export)} not implemented for "
15041517
f"mixed-domain approach"
15051518
)
1506-
else:
1507-
raise NotImplementedError(f"Export type {type(export)} not implemented")
1519+
1520+
# compute diffusivity function for surface fluxes
1521+
1522+
spe_to_D_global = {} # links species to global D function
1523+
spe_to_D_global_expr = {} # links species to D expression
1524+
1525+
for export in self.exports:
1526+
if isinstance(export, exports.SurfaceQuantity):
1527+
if export.field in spe_to_D_global:
1528+
# if already computed then use the same D
1529+
D = spe_to_D_global[export.field]
1530+
D_expr = spe_to_D_global_expr[export.field]
1531+
else:
1532+
# compute D and add it to the dict
1533+
D, D_expr = self.define_D_global(export.field)
1534+
spe_to_D_global[export.field] = D
1535+
spe_to_D_global_expr[export.field] = D_expr
1536+
1537+
# add the global D to the export
1538+
export.D = D
1539+
export.D_expr = D_expr
1540+
1541+
# reset the data and time for SurfaceQuantity and VolumeQuantity
1542+
if isinstance(export, exports.SurfaceQuantity | exports.VolumeQuantity):
1543+
export.t = []
1544+
export.data = []
15081545

15091546
def post_processing(self):
15101547
# update post-processing solutions (for each species in each subdomain)
@@ -1536,6 +1573,56 @@ def post_processing(self):
15361573
if export.is_it_time_to_export(float(self.t)):
15371574
export.writer.write(float(self.t))
15381575

1576+
# handle derived quantities
1577+
if isinstance(export, exports.SurfaceQuantity):
1578+
if isinstance(
1579+
export,
1580+
exports.SurfaceFlux | exports.TotalSurface | exports.AverageSurface,
1581+
):
1582+
if len(self.advection_terms) > 0:
1583+
warnings.warn(
1584+
"Advection terms are not currently accounted for in the "
1585+
"evaluation of surface flux values"
1586+
)
1587+
export_surf = export.surface
1588+
export_vol = self.surface_to_volume[export_surf]
1589+
submesh_function = (
1590+
export.field.subdomain_to_post_processing_solution[export_vol]
1591+
)
1592+
export.compute(
1593+
u=submesh_function,
1594+
ds=self.ds,
1595+
entity_maps={
1596+
sd.submesh: sd.parent_to_submesh
1597+
for sd in self.volume_subdomains
1598+
},
1599+
)
1600+
else:
1601+
export.compute()
1602+
1603+
elif isinstance(export, exports.VolumeQuantity):
1604+
if isinstance(export, exports.TotalVolume | exports.AverageVolume):
1605+
export.compute(
1606+
u=export.field.subdomain_to_post_processing_solution[
1607+
export_vol
1608+
],
1609+
dx=self.dx,
1610+
entity_maps={
1611+
sd.submesh: sd.parent_to_submesh
1612+
for sd in self.volume_subdomains
1613+
},
1614+
)
1615+
else:
1616+
export.compute()
1617+
1618+
if isinstance(export, exports.SurfaceQuantity | exports.VolumeQuantity):
1619+
# update export data
1620+
export.t.append(float(self.t))
1621+
1622+
# if filename given write export data to file
1623+
if export.filename is not None:
1624+
export.write(t=float(self.t))
1625+
15391626
def iterate(self):
15401627
"""Iterates the model for a given time step"""
15411628
if self.show_progress_bar:

test/system_tests/test_multi_mat_penalty.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,74 @@ def c_exact_bot_np(x):
161161

162162
assert L2_error_top < 1e-3
163163
assert L2_error_bot < 1e-3
164+
165+
166+
def test_derived_quantities_multi_mat():
167+
K_S_top = 1.0
168+
K_S_bot = 1.0
169+
D_top = 2.0
170+
D_bot = 1.0
171+
172+
mesh, mt, ct = generate_mesh(20)
173+
174+
my_model = F.HydrogenTransportProblemDiscontinuous()
175+
my_model.mesh = F.Mesh(mesh)
176+
my_model.volume_meshtags = ct
177+
my_model.facet_meshtags = mt
178+
179+
material_top = F.Material(D_0=D_top, E_D=0, K_S_0=K_S_top, E_K_S=0)
180+
material_bottom = F.Material(D_0=D_bot, E_D=0, K_S_0=K_S_bot, E_K_S=0)
181+
182+
material_top.solubility_law = "sievert"
183+
material_bottom.solubility_law = "sievert"
184+
185+
top_domain = F.VolumeSubdomain(4, material=material_top)
186+
bottom_domain = F.VolumeSubdomain(3, material=material_bottom)
187+
188+
top_surface = F.SurfaceSubdomain(id=1)
189+
bottom_surface = F.SurfaceSubdomain(id=2)
190+
my_model.subdomains = [
191+
bottom_domain,
192+
top_domain,
193+
top_surface,
194+
bottom_surface,
195+
]
196+
197+
my_model.interfaces = [
198+
F.Interface(5, (bottom_domain, top_domain), penalty_term=1),
199+
]
200+
my_model.surface_to_volume = {
201+
top_surface: top_domain,
202+
bottom_surface: bottom_domain,
203+
}
204+
205+
H = F.Species("H", mobile=True)
206+
207+
my_model.species = [H]
208+
209+
for species in my_model.species:
210+
species.subdomains = [bottom_domain, top_domain]
211+
212+
my_model.boundary_conditions = [
213+
F.FixedConcentrationBC(top_surface, value=1, species=H),
214+
F.FixedConcentrationBC(bottom_surface, value=0, species=H),
215+
]
216+
217+
my_model.temperature = 500.0
218+
219+
my_model.settings = F.Settings(atol=1e-10, rtol=1e-10, transient=False)
220+
my_model.exports = [
221+
F.SurfaceFlux(field=H, surface=top_surface),
222+
F.SurfaceFlux(field=H, surface=bottom_surface),
223+
F.AverageVolume(field=H, volume=bottom_domain),
224+
F.AverageVolume(field=H, volume=top_domain),
225+
F.TotalVolume(field=H, volume=bottom_domain),
226+
F.TotalVolume(field=H, volume=top_domain),
227+
]
228+
229+
my_model.initialise()
230+
my_model.run()
231+
232+
print("Top surface flux:", my_model.exports[0].data)
233+
print("Bottom surface flux:", my_model.exports[1].data)
234+
assert np.isclose(my_model.exports[0].data[0], -my_model.exports[1].data[0])

test/system_tests/test_surface_reactions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, reaction: F.SurfaceReactionBC):
1414
)
1515
self.reaction = reaction.flux_bcs[0]
1616

17-
def compute(self, ds):
17+
def compute(self, u, ds, entity_maps=None):
1818
self.value = fem.assemble_scalar(
1919
fem.form(self.reaction.value_fenics * ds(self.surface.id))
2020
)

test/test_average_volume.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_average_volume_compute_1D():
3030
my_export = F.AverageVolume(field=my_species, volume=dummy_volume)
3131

3232
# RUN
33-
my_export.compute(dx)
33+
my_export.compute(my_species.solution, dx)
3434

3535
# TEST
3636
expected_value = 3.5

test/test_surface_quantity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_surface_flux_export_compute():
4545
my_export.D = D
4646

4747
# RUN
48-
my_export.compute(ds=ds)
48+
my_export.compute(my_species.solution, ds=ds)
4949

5050
# TEST
5151
# flux = -D grad(c)_ \cdot n = -D dc/dx = -D * 4 * x

test/test_total_volume.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_total_volume_export_compute():
3636
)
3737

3838
# RUN
39-
my_export.compute(dx=dx)
39+
my_export.compute(my_species.solution, dx=dx)
4040

4141
# TEST
4242
# total = int[0,L] c dx = 2/3 * L^3 + L

0 commit comments

Comments
 (0)