diff --git a/.github/workflows/ci_conda.yml b/.github/workflows/ci_conda.yml index 9c9c500..6d577b1 100644 --- a/.github/workflows/ci_conda.yml +++ b/.github/workflows/ci_conda.yml @@ -3,24 +3,29 @@ on: [pull_request, push] jobs: run-tests: + name: run-tests (fenics-dolfinx v${{ matrix.dolfinx }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + dolfinx: ['0.9.0', '0.10.0'] + steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Conda uses: conda-incubator/setup-miniconda@v3 with: activate-environment: myenv miniforge-version: latest - # use-mamba: true channels: conda-forge - name: Create Conda environment shell: bash -l {0} run: | - conda install -c conda-forge fenics-dolfinx=0.9.0 pyvista + conda install -c conda-forge fenics-dolfinx=${{ matrix.dolfinx }} pyvista - name: Install local package and dependencies shell: bash -l {0} diff --git a/.github/workflows/ci_docker.yml b/.github/workflows/ci_docker.yml index e7142ae..43a3e4a 100644 --- a/.github/workflows/ci_docker.yml +++ b/.github/workflows/ci_docker.yml @@ -7,11 +7,15 @@ jobs: strategy: fail-fast: false matrix: - container_version: [v0.9.0, nightly] + container_version: [v0.9.0, v0.10.0, nightly] container: dolfinx/dolfinx:${{ matrix.container_version }} steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 + + - name: Configure git safe directory + run: | + git config --global --add safe.directory '*' - name: Install local package and dependencies run: | diff --git a/src/foam2dolfinx/__init__.py b/src/foam2dolfinx/__init__.py index 51a78a4..880e266 100644 --- a/src/foam2dolfinx/__init__.py +++ b/src/foam2dolfinx/__init__.py @@ -8,4 +8,4 @@ from .open_foam_reader import OpenFOAMReader, find_closest_value -__all__ = ["OpenFOAMReader, find_closest_value"] +__all__ = ["OpenFOAMReader", "find_closest_value"] diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index a17b938..a86d50f 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -168,15 +168,19 @@ def _create_dolfinx_mesh(self, subdomain: str | None = "default"): ) degree = 1 # Set polynomial degree cell = ufl.Cell(shape) + # ufl.Cell.cellname became a property after dolfinx v0.10 + cell_name = cell.cellname() if callable(cell.cellname) else cell.cellname self.mesh_vector_element = basix.ufl.element( - "Lagrange", cell.cellname(), degree, shape=(3,) + "Lagrange", cell_name, degree, shape=(3,) ) self.mesh_scalar_element = basix.ufl.element( - "Lagrange", cell.cellname(), degree, shape=() + "Lagrange", cell_name, degree, shape=() ) # Create dolfinx Mesh - mesh_ufl = ufl.Mesh(self.mesh_vector_element) + mesh_ufl = ufl.Mesh( + basix.ufl.element("Lagrange", cell_name, degree, shape=(3,)) + ) self.dolfinx_meshes_dict[subdomain] = create_mesh( comm=MPI.COMM_WORLD, cells=self.connectivities_dict[subdomain], @@ -184,10 +188,10 @@ def _create_dolfinx_mesh(self, subdomain: str | None = "default"): e=mesh_ufl, ) - def create_dolfinx_function( + def create_dolfinx_function_with_cell_data( self, t: float, name: str = "U", subdomain: str | None = "default" ) -> dolfinx.fem.Function: - """Creates a dolfinx.fem.Function from the OpenFOAM file. + """Creates a dolfinx.fem.Function from the OpenFOAM file using cell data. Args: t: timestamp of the data to read @@ -202,6 +206,66 @@ def create_dolfinx_function( # read the OpenFOAM data in the filename provided self._read_with_pyvista(t=t, subdomain=subdomain) + # test if name is in the cell data of the OF mesh + if name not in self.OF_meshes_dict[subdomain].cell_data.keys(): + raise ValueError( + f"Function name: {name} not found in the subdomain: {subdomain}, " + "in the OpenFOAM file. " + f"Available functions in subdomain: {subdomain} : " + f"{self.OF_meshes_dict[subdomain].cell_data.keys()}" + ) + + # create the dolfinx mesh + if subdomain not in self.dolfinx_meshes_dict: + self._create_dolfinx_mesh(subdomain=subdomain) + + mesh = self.dolfinx_meshes_dict[subdomain] + + if name == "U": + element = basix.ufl.element("DG", mesh.topology.cell_name(), 0, shape=(3,)) + else: + element = basix.ufl.element("DG", mesh.topology.cell_name(), 0, shape=()) + + function_space = dolfinx.fem.functionspace(mesh, element) + u = dolfinx.fem.Function(function_space) + + # Assign values in OF_mesh to dolfinx_mesh + assert hasattr(self.OF_meshes_dict[subdomain], "cell_data") + u.x.array[:] = ( + self.OF_meshes_dict[subdomain] + .cell_data[name][mesh.topology.original_cell_index] + .flatten() + ) + + return u + + def create_dolfinx_function_with_point_data( + self, t: float, name: str = "U", subdomain: str | None = "default" + ) -> dolfinx.fem.Function: + """Creates a dolfinx.fem.Function from the OpenFOAM file using point data. + + Args: + t: timestamp of the data to read + name: Name of the field in the OpenFOAM file, defaults to "U" for velocity + subdomain: Name of the subdmain in the OpenFOAM file, from which a field is + extracted + + Returns: + the dolfinx function + """ + + # read the OpenFOAM data in the filename provided + self._read_with_pyvista(t=t, subdomain=subdomain) + + # test if name is in the cell data of the OF mesh + if name not in self.OF_meshes_dict[subdomain].cell_data.keys(): + raise ValueError( + f"Function name: {name} not found in the subdomain: {subdomain}, " + "in the OpenFOAM file. " + f"Available functions in subdomain: {subdomain} : " + f"{self.OF_meshes_dict[subdomain].cell_data.keys()}" + ) + # create the dolfinx mesh if subdomain not in self.dolfinx_meshes_dict: self._create_dolfinx_mesh(subdomain=subdomain) diff --git a/test/test_create_dolfinx_mesh.py b/test/test_create_dolfinx_mesh.py index d0958b1..f19a897 100644 --- a/test/test_create_dolfinx_mesh.py +++ b/test/test_create_dolfinx_mesh.py @@ -1,7 +1,8 @@ +import re + import numpy as np import pytest from pyvista import examples -import re from foam2dolfinx import OpenFOAMReader @@ -19,7 +20,10 @@ def test_error_rasied_when_using_mixed_topology_mesh(): # Create a 400x8 array filled with random values my_reader.OF_cells_dict["default"] = rng.random((400, 8)) - error_message = "Cell type: 1, not supported, please use either 12 (hexahedron) or 10 (tetrahedron) cells in OF mesh" + error_message = ( + "Cell type: 1, not supported, please use either 12 (hexahedron) " + "or 10 (tetrahedron) cells in OF mesh" + ) pattern = re.escape(error_message) with pytest.raises( diff --git a/test/test_create_function.py b/test/test_create_function.py new file mode 100644 index 0000000..736a06d --- /dev/null +++ b/test/test_create_function.py @@ -0,0 +1,59 @@ +import re +import zipfile +from pathlib import Path + +import pytest + +from foam2dolfinx import OpenFOAMReader + + +def test_not_finding_function_cell_data(tmpdir): + zip_path = Path("test/data/test_2Regions.zip") + extract_path = Path(tmpdir) / "test_2Regions" + + # Unzip the file + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(extract_path) + + # Construct the path to the .foam file + foam_file = extract_path / "test_2Regions/pv.foam" + + # read the .foam file + my_of_reader = OpenFOAMReader(filename=str(foam_file), cell_type=12) + + with pytest.raises( + ValueError, + match=re.escape( + "Function name: coucou not found in the subdomain: solid, in the OpenFOAM file. " + "Available functions in subdomain: solid : ['T']" + ), + ): + my_of_reader.create_dolfinx_function_with_cell_data( + t=20.0, subdomain="solid", name="coucou" + ) + + +def test_not_finding_function_point_data(tmpdir): + zip_path = Path("test/data/test_2Regions.zip") + extract_path = Path(tmpdir) / "test_2Regions" + + # Unzip the file + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(extract_path) + + # Construct the path to the .foam file + foam_file = extract_path / "test_2Regions/pv.foam" + + # read the .foam file + my_of_reader = OpenFOAMReader(filename=str(foam_file), cell_type=12) + + with pytest.raises( + ValueError, + match=re.escape( + "Function name: coucou not found in the subdomain: solid, in the OpenFOAM file. " + "Available functions in subdomain: solid : ['T']" + ), + ): + my_of_reader.create_dolfinx_function_with_point_data( + t=20.0, subdomain="solid", name="coucou" + ) diff --git a/test/test_example.py b/test/test_example.py index cd4f24d..5b971bf 100644 --- a/test/test_example.py +++ b/test/test_example.py @@ -1,13 +1,15 @@ -from foam2dolfinx import OpenFOAMReader -import dolfinx -from pyvista import examples import zipfile from pathlib import Path +import dolfinx +from pyvista import examples + +from foam2dolfinx import OpenFOAMReader + def test_reading_and_writing_cavity_example(): my_of_reader = OpenFOAMReader(filename=examples.download_cavity(load=False)) - vel = my_of_reader.create_dolfinx_function(t=2.5) + vel = my_of_reader.create_dolfinx_function_with_point_data(t=2.5) assert isinstance(vel, dolfinx.fem.Function) @@ -28,8 +30,8 @@ def test_baby_example(tmpdir): # read the .foam file my_of_reader = OpenFOAMReader(filename=str(foam_file), cell_type=10) - vel = my_of_reader.create_dolfinx_function(t=time, name="U") - T = my_of_reader.create_dolfinx_function(t=time, name="T") + vel = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="U") + T = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="T") assert isinstance(vel, dolfinx.fem.Function) assert isinstance(T, dolfinx.fem.Function) @@ -51,10 +53,44 @@ def test_hot_room(tmpdir): # read the .foam file my_of_reader = OpenFOAMReader(filename=str(foam_file), cell_type=12) - vel = my_of_reader.create_dolfinx_function(t=time, name="U") - T = my_of_reader.create_dolfinx_function(t=time, name="T") - nut = my_of_reader.create_dolfinx_function(t=time, name="nut") + vel_point = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="U") + T_point = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="T") + nut_point = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="nut") - assert isinstance(vel, dolfinx.fem.Function) - assert isinstance(T, dolfinx.fem.Function) - assert isinstance(nut, dolfinx.fem.Function) + vel_cell = my_of_reader.create_dolfinx_function_with_cell_data(t=time, name="U") + T_cell = my_of_reader.create_dolfinx_function_with_cell_data(t=time, name="T") + nut_cell = my_of_reader.create_dolfinx_function_with_cell_data(t=time, name="nut") + + assert isinstance(vel_point, dolfinx.fem.Function) + assert isinstance(T_point, dolfinx.fem.Function) + assert isinstance(nut_point, dolfinx.fem.Function) + assert isinstance(vel_cell, dolfinx.fem.Function) + assert isinstance(T_cell, dolfinx.fem.Function) + assert isinstance(nut_cell, dolfinx.fem.Function) + + +def test_mesh_created_when_not_in_dict(tmpdir): + zip_path = Path("test/data/test_2Regions.zip") + extract_path = Path(tmpdir) / "test_2Regions" + + # Unzip the file + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(extract_path) + + # Construct the path to the .foam file + foam_file = extract_path / "test_2Regions/pv.foam" + + # read the .foam file + my_of_reader = OpenFOAMReader(filename=str(foam_file), cell_type=12) + + my_of_reader.create_dolfinx_function_with_cell_data( + t=20.0, subdomain="fluid", name="T" + ) + + assert len(my_of_reader.dolfinx_meshes_dict) == 1 + + my_of_reader.create_dolfinx_function_with_cell_data( + t=20.0, subdomain="solid", name="T" + ) + + assert len(my_of_reader.dolfinx_meshes_dict) == 2 diff --git a/test/test_read_with_pyvista.py b/test/test_read_with_pyvista.py index 443ac67..eb72f96 100644 --- a/test/test_read_with_pyvista.py +++ b/test/test_read_with_pyvista.py @@ -44,7 +44,10 @@ def test_error_rasied_when_subdomain_is_not_given_in_multidomain_case(tmpdir): with pytest.raises( ValueError, - match="Subdomain None not found in the OpenFOAM file\. Available subdomains: \['defaultRegion', 'fluid', 'solid']", + match=( + r"Subdomain None not found in the OpenFOAM file\. " + r"Available subdomains: \['defaultRegion', 'fluid', 'solid']" + ), ): my_of_reader._read_with_pyvista(t=20.0, subdomain=None)