Skip to content

Commit 3c031ab

Browse files
Merge pull request #1017 from festim-dev/is-it-time
Add `times` argument to ProfileExport
2 parents a298c22 + ec09817 commit 3c031ab

File tree

8 files changed

+123
-77
lines changed

8 files changed

+123
-77
lines changed

src/festim/exports/profile_1d.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,41 @@ class Profile1DExport:
1010
field: the species for which the profile is computed.
1111
subdomain: the volume subdomain to compute the profile on. If None, the profile
1212
is computed over the entire domain.
13+
times: if provided, the profile will be exported at these timesteps.
14+
Otherwise, exports at all timesteps. Defaults to None.
1315
1416
Attributes:
1517
field: the species for which the profile is computed.
1618
subdomain: the volume subdomain to compute the profile on. If None, the profile
1719
is computed over the entire domain.
20+
times: if provided, the profile will be exported at these timesteps. Otherwise,
21+
exports at all timesteps.
1822
x: the coordinates along which the profile is computed.
1923
data: the computed profile data.
24+
t: the list of time values at which the profile is computed.
2025
"""
2126

2227
x: np.ndarray
2328
data: list
2429
field: Species
2530
subdomain: VolumeSubdomain | None
31+
times: list[float] | None
32+
t: list[float]
2633
_dofs: np.ndarray
2734
_sort_coords: np.ndarray
2835

29-
def __init__(self, field: Species, subdomain: VolumeSubdomain = None):
36+
def __init__(
37+
self,
38+
field: Species,
39+
subdomain: VolumeSubdomain = None,
40+
times: list[float] | None = None,
41+
):
3042
self.field = field
3143
self.data = []
44+
self.t = []
3245
self.x = None
3346
self.subdomain = subdomain
47+
self.times = times
3448

3549
self._dofs = None
3650
self._sort_coords = None

src/festim/exports/vtx.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,6 @@ def __init__(
4949
def filename(self):
5050
return self._filename
5151

52-
def is_it_time_to_export(self, current_time: float) -> bool:
53-
"""
54-
Checks if the exported field should be written to a file or not based on the
55-
current time and the times in `export.times`
56-
57-
Args:
58-
current_time: the current simulation time
59-
60-
Returns:
61-
bool: True if the exported field should be written to a file, else False
62-
"""
63-
64-
if self.times is None:
65-
return True
66-
67-
for time in self.times:
68-
if np.isclose(time, current_time, atol=0):
69-
return True
70-
71-
return False
72-
7352

7453
class VTXTemperatureExport(ExportBaseClass):
7554
"""Export temperature field functions to VTX file

src/festim/heat_transfer_problem.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ def post_processing(self):
236236
"""Post processes the model"""
237237

238238
for export in self.exports:
239+
# skip if it isn't time to export
240+
if hasattr(export, "times"):
241+
if not helpers.is_it_time_to_export(
242+
current_time=float(self.t), times=export.times
243+
):
244+
continue
239245
# TODO if export type derived quantity
240246
if isinstance(export, exports.SurfaceQuantity):
241247
raise NotImplementedError(
@@ -256,8 +262,7 @@ def post_processing(self):
256262
export.write(float(self.t))
257263

258264
if isinstance(export, exports.VTXTemperatureExport):
259-
if export.is_it_time_to_export(float(self.t)):
260-
export.writer.write(float(self.t))
265+
export.writer.write(float(self.t))
261266

262267
def __del__(self):
263268
for export in self.exports:

src/festim/helpers.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,29 @@ def nmm_interpolate(
317317
f_out.function_space, f_in.function_space, cells, padding=padding
318318
)
319319
f_out.interpolate_nonmatching(f_in, cells, interpolation_data=interpolation_data)
320+
321+
322+
def is_it_time_to_export(
323+
times: list | None, current_time: float, atol=0, rtol=1.0e-5
324+
) -> bool:
325+
"""
326+
Checks if the exported field should be written to a file or not based on the
327+
current time and the times in `export.times`
328+
329+
Args:
330+
current_time: the current simulation time
331+
atol: absolute tolerance for time comparison
332+
rtol: relative tolerance for time comparison
333+
times: the times at which the field should be exported, if None, returns True
334+
335+
Returns:
336+
bool: True if the exported field should be written to a file, else False
337+
"""
338+
if times is None:
339+
return True
340+
341+
for time in times:
342+
if np.isclose(time, current_time, atol=atol, rtol=rtol):
343+
return True
344+
345+
return False

src/festim/hydrogen_transport_problem.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
subdomain as _subdomain,
3434
)
3535
from festim.advection import AdvectionTerm
36-
from festim.helpers import as_fenics_constant, get_interpolation_points
36+
from festim.helpers import (
37+
as_fenics_constant,
38+
get_interpolation_points,
39+
is_it_time_to_export,
40+
)
3741
from festim.mesh import Mesh
3842

3943
__all__ = ["HydrogenTransportProblemDiscontinuous", "HydrogenTransportProblem"]
@@ -916,28 +920,34 @@ def post_processing(self):
916920
species_not_updated.remove(export.field)
917921

918922
for export in self.exports:
923+
# skip if it isn't time to export
924+
if hasattr(export, "times"):
925+
if not is_it_time_to_export(
926+
current_time=float(self.t), times=export.times
927+
):
928+
continue
929+
919930
# handle VTX exports
920931
if isinstance(export, exports.ExportBaseClass):
921-
if export.is_it_time_to_export(float(self.t)):
922-
if isinstance(export, exports.VTXSpeciesExport):
923-
if export._checkpoint:
924-
for field in export.field:
925-
adios4dolfinx.write_function(
926-
export.filename,
927-
field.post_processing_solution,
928-
time=float(self.t),
929-
name=field.name,
930-
)
931-
else:
932-
export.writer.write(float(self.t))
933-
elif (
934-
isinstance(export, festim.VTXTemperatureExport)
935-
and self.temperature_time_dependent
936-
):
937-
self._temperature_as_function.interpolate(
938-
self._get_temperature_field_as_function()
939-
)
932+
if isinstance(export, exports.VTXSpeciesExport):
933+
if export._checkpoint:
934+
for field in export.field:
935+
adios4dolfinx.write_function(
936+
export.filename,
937+
field.post_processing_solution,
938+
time=float(self.t),
939+
name=field.name,
940+
)
941+
else:
940942
export.writer.write(float(self.t))
943+
elif (
944+
isinstance(export, festim.VTXTemperatureExport)
945+
and self.temperature_time_dependent
946+
):
947+
self._temperature_as_function.interpolate(
948+
self._get_temperature_field_as_function()
949+
)
950+
export.writer.write(float(self.t))
941951

942952
# TODO if export type derived quantity
943953
if isinstance(export, exports.SurfaceQuantity):
@@ -1000,6 +1010,7 @@ def post_processing(self):
10001010
else:
10011011
c = self.u.x.array[export._dofs]
10021012
export.data.append(c)
1013+
export.t.append(float(self.t))
10031014

10041015

10051016
class HydrogenTransportProblemDiscontinuous(HydrogenTransportProblem):
@@ -1593,6 +1604,12 @@ def post_processing(self):
15931604
collapsed_function.x.array[:] = u.x.array[v0_to_V]
15941605

15951606
for export in self.exports:
1607+
# skip if it isn't time to export
1608+
if hasattr(export, "times"):
1609+
if not is_it_time_to_export(
1610+
current_time=float(self.t), times=export.times
1611+
):
1612+
continue
15961613
# handle VTX exports
15971614
if isinstance(export, exports.ExportBaseClass):
15981615
if not isinstance(export, exports.VTXSpeciesExport):
@@ -1605,8 +1622,7 @@ def post_processing(self):
16051622
f"Export type {type(export)} not implemented "
16061623
f"for mixed-domain approach"
16071624
)
1608-
if export.is_it_time_to_export(float(self.t)):
1609-
export.writer.write(float(self.t))
1625+
export.writer.write(float(self.t))
16101626

16111627
# handle derived quantities
16121628
if isinstance(export, exports.SurfaceQuantity):
@@ -1676,6 +1692,7 @@ def post_processing(self):
16761692
c = u.x.array[export._dofs][export._sort_coords]
16771693

16781694
export.data.append(c)
1695+
export.t.append(float(self.t))
16791696

16801697
def iterate(self):
16811698
"""Iterates the model for a given time step"""

test/test_helpers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,28 @@ def example_func(t):
297297
match=f"A time dependent advection field should return an fem.Function, not a <class 'ufl.algebra.Product'>",
298298
):
299299
test_value.convert_input_value(function_space=test_function_space, t=t)
300+
301+
302+
@pytest.mark.parametrize(
303+
"input, expected_output",
304+
[
305+
(2, True),
306+
(3, True),
307+
(1, True),
308+
(-1, False),
309+
(0, False),
310+
(5, False),
311+
(1.5, False),
312+
],
313+
)
314+
def test_is_it_time_to_export(input, expected_output):
315+
times = [1, 2, 3]
316+
assert (
317+
F.helpers.is_it_time_to_export(current_time=input, times=times)
318+
== expected_output
319+
)
320+
321+
322+
def test_is_it_time_to_export_when_times_not_given():
323+
times = None
324+
assert F.helpers.is_it_time_to_export(current_time=1.0, times=times)

test/test_profile.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import festim as F
22
import numpy as np
3+
import pytest
34

45

5-
def test_profile():
6+
@pytest.mark.parametrize("times", [None, [2, 5], [5]])
7+
def test_profile(times):
68
my_model = F.HydrogenTransportProblem()
79

810
protium = F.Species("H")
@@ -41,17 +43,21 @@ def test_profile():
4143
my_model.settings.stepsize = F.Stepsize(1)
4244

4345
my_model.exports = [
44-
F.Profile1DExport(protium),
45-
F.Profile1DExport(deuterium),
46+
F.Profile1DExport(protium, times=times),
47+
F.Profile1DExport(deuterium, times=times),
4648
]
4749

4850
my_model.initialise()
4951
my_model.run()
5052

5153
assert my_model.exports[0].x is not None
5254
assert my_model.exports[1].x is not None
53-
assert len(my_model.exports[0].data) > 0
54-
assert len(my_model.exports[1].data) > 0
55+
if times is None:
56+
assert len(my_model.exports[0].data) > 0
57+
assert len(my_model.exports[1].data) > 0
58+
else:
59+
assert len(my_model.exports[0].data) == len(times)
60+
assert len(my_model.exports[1].data) == len(times)
5561

5662

5763
def test_profile_single_species():

test/test_vtx.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -179,29 +179,3 @@ def test_filename_temp_raises_error_when_wrong_type():
179179
"""Test that the filename attribute for VTXTemperature export raises an error if the extension is not .bp"""
180180
with pytest.raises(TypeError):
181181
F.VTXTemperatureExport(1)
182-
183-
184-
@pytest.mark.parametrize(
185-
"input, expected_output",
186-
[
187-
(2, True),
188-
(3, True),
189-
(1, True),
190-
(-1, False),
191-
(0, False),
192-
(5, False),
193-
(1.5, False),
194-
],
195-
)
196-
def test_is_it_time_to_export(tmpdir, input, expected_output):
197-
filename = str(tmpdir.join("my_T_export.bp"))
198-
my_export = F.ExportBaseClass(times=[1, 2, 3], ext=".bp", filename=filename)
199-
200-
assert my_export.is_it_time_to_export(input) == expected_output
201-
202-
203-
def test_is_it_time_to_export_when_times_not_given(tmpdir):
204-
filename = str(tmpdir.join("my_T_export.bp"))
205-
my_export = F.ExportBaseClass(ext=".bp", filename=filename)
206-
207-
assert my_export.is_it_time_to_export(1.0)

0 commit comments

Comments
 (0)