From 7b37870331117c33015dca342d9a9dbb0bb3fa67 Mon Sep 17 00:00:00 2001 From: jhdark Date: Tue, 3 Mar 2026 11:51:46 -0500 Subject: [PATCH 01/12] new options for cell or point data --- src/foam2dolfinx/open_foam_reader.py | 51 ++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index a17b938..2b4fbac 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -176,7 +176,9 @@ def _create_dolfinx_mesh(self, subdomain: str | None = "default"): ) # Create dolfinx Mesh - mesh_ufl = ufl.Mesh(self.mesh_vector_element) + mesh_ufl = ufl.Mesh( + basix.ufl.element("Lagrange", cell.cellname(), degree, shape=(3,)) + ) self.dolfinx_meshes_dict[subdomain] = create_mesh( comm=MPI.COMM_WORLD, cells=self.connectivities_dict[subdomain], @@ -184,7 +186,7 @@ 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. @@ -208,6 +210,50 @@ def create_dolfinx_function( 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. + + 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 + """ + + assert hasattr(self.OF_meshes_dict[subdomain], "point_data") + + # read the OpenFOAM data in the filename provided + self._read_with_pyvista(t=t, subdomain=subdomain) + + # 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 = self.mesh_vector_element else: @@ -239,7 +285,6 @@ def create_dolfinx_function( ][cell_indices, vertex_positions] # Assign values in OF_mesh to dolfinx_mesh - assert hasattr(self.OF_meshes_dict[subdomain], "point_data") u.x.array[:] = ( self.OF_meshes_dict[subdomain].point_data[name][vertex_map].flatten() ) From 773279b3f6e97bf381e6454fbaedb4cee1fc37fa Mon Sep 17 00:00:00 2001 From: jhdark Date: Tue, 3 Mar 2026 11:54:21 -0500 Subject: [PATCH 02/12] move test --- src/foam2dolfinx/open_foam_reader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index 2b4fbac..66e9cd9 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -243,8 +243,6 @@ def create_dolfinx_function_with_point_data( the dolfinx function """ - assert hasattr(self.OF_meshes_dict[subdomain], "point_data") - # read the OpenFOAM data in the filename provided self._read_with_pyvista(t=t, subdomain=subdomain) @@ -285,6 +283,7 @@ def create_dolfinx_function_with_point_data( ][cell_indices, vertex_positions] # Assign values in OF_mesh to dolfinx_mesh + assert hasattr(self.OF_meshes_dict[subdomain], "point_data") u.x.array[:] = ( self.OF_meshes_dict[subdomain].point_data[name][vertex_map].flatten() ) From 5d87990b108792a58b9f8c1fb14cc41ad361c721 Mon Sep 17 00:00:00 2001 From: jhdark Date: Tue, 3 Mar 2026 11:54:30 -0500 Subject: [PATCH 03/12] update tests --- test/test_example.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/test_example.py b/test/test_example.py index cd4f24d..1231ab5 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,9 +53,9 @@ 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 = 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") + nut = 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) From 142c8d46a2be9070483de7b91a038881922df8e1 Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:23:06 -0400 Subject: [PATCH 04/12] format ruff Co-authored-by: Copilot --- src/foam2dolfinx/__init__.py | 2 +- test/test_create_dolfinx_mesh.py | 8 ++++++-- test/test_read_with_pyvista.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) 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/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_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) From 81c0727b996fe9ba9b20d35cb207e9bd9d7e3921 Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:29:27 -0400 Subject: [PATCH 05/12] test on dolfinx v0.9 and v0.10 --- .github/workflows/ci_conda.yml | 11 ++++++++--- .github/workflows/ci_docker.yml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) 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..1a00455 100644 --- a/.github/workflows/ci_docker.yml +++ b/.github/workflows/ci_docker.yml @@ -7,7 +7,7 @@ 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 From 9841bbc4c8efa2e4d47cee8ec7d2a4d38c2f582b Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:34:49 -0400 Subject: [PATCH 06/12] open in safe directory --- .github/workflows/ci_docker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_docker.yml b/.github/workflows/ci_docker.yml index 1a00455..43a3e4a 100644 --- a/.github/workflows/ci_docker.yml +++ b/.github/workflows/ci_docker.yml @@ -11,7 +11,11 @@ jobs: 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: | From ea1d8844aaaabaecde8866bef938888100a3049b Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:40:51 -0400 Subject: [PATCH 07/12] test reading cell data --- test/test_example.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/test_example.py b/test/test_example.py index 1231ab5..8c5c9e0 100644 --- a/test/test_example.py +++ b/test/test_example.py @@ -53,10 +53,17 @@ 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_with_point_data(t=time, name="U") - T = my_of_reader.create_dolfinx_function_with_point_data(t=time, name="T") - nut = 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_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") + + 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) From 0b942959477a5cc4e1d83e3ebc61d0ab58c624e7 Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:42:46 -0400 Subject: [PATCH 08/12] add some more details Co-authored-by: Copilot --- src/foam2dolfinx/open_foam_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index 66e9cd9..b24db58 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -189,7 +189,7 @@ def _create_dolfinx_mesh(self, subdomain: str | None = "default"): 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 @@ -231,7 +231,7 @@ def create_dolfinx_function_with_cell_data( 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. + """Creates a dolfinx.fem.Function from the OpenFOAM file using point data. Args: t: timestamp of the data to read From 1d4e7b06f0a54a049450c3430644bea2393c5bd2 Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 20:57:45 -0400 Subject: [PATCH 09/12] test new meshes are generated --- test/test_example.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/test_example.py b/test/test_example.py index 8c5c9e0..5b971bf 100644 --- a/test/test_example.py +++ b/test/test_example.py @@ -67,3 +67,30 @@ def test_hot_room(tmpdir): 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 From a7c6f577ced2807245aa0d35d518dff1f6a4e7ea Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 21:15:09 -0400 Subject: [PATCH 10/12] test if function is in subdomain Co-authored-by: Copilot --- src/foam2dolfinx/open_foam_reader.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index b24db58..5d8fcce 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -204,6 +204,15 @@ def create_dolfinx_function_with_cell_data( # 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) @@ -246,6 +255,15 @@ def create_dolfinx_function_with_point_data( # 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) From 17f479097d5080b02a04120cebfbfe488fee3aeb Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 21:15:14 -0400 Subject: [PATCH 11/12] test for test Co-authored-by: Copilot --- test/test_create_function.py | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/test_create_function.py 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" + ) From 312e5afc1634550b3ec8c3272779c332146869cc Mon Sep 17 00:00:00 2001 From: jhdark Date: Thu, 23 Apr 2026 21:23:08 -0400 Subject: [PATCH 12/12] not callable value in nightly --- src/foam2dolfinx/open_foam_reader.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/foam2dolfinx/open_foam_reader.py b/src/foam2dolfinx/open_foam_reader.py index 5d8fcce..a86d50f 100644 --- a/src/foam2dolfinx/open_foam_reader.py +++ b/src/foam2dolfinx/open_foam_reader.py @@ -168,16 +168,18 @@ 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( - basix.ufl.element("Lagrange", cell.cellname(), degree, shape=(3,)) + basix.ufl.element("Lagrange", cell_name, degree, shape=(3,)) ) self.dolfinx_meshes_dict[subdomain] = create_mesh( comm=MPI.COMM_WORLD,