DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_fv_correctness.py
Go to the documentation of this file.
1"""
2Correctness tests for CFV.FiniteVolume through the Python bindings.
3
4Tests the FiniteVolume construction pipeline and verifies geometric
5quantities (volumes, areas, barycenters, normals) against known values
6for the Uniform_3x3_wall mesh (a 3x3 quad grid on [-1,2]^2).
7
8These tests use pytest assertions and are intended for automated CI.
9"""
10
11from __future__ import annotations
12
13import os
14import sys
15import numpy as np
16import pytest
17
18from DNDSR import DNDS, Geom, CFV
19from DNDSR.Geom.utils import create_mesh_from_CGNS, create_bnd_mesh
20
21
22@pytest.fixture(scope="module")
23def mpi():
24 m = DNDS.MPIInfo()
25 m.setWorld()
26 return m
27
28
29def _data_mesh_path(name: str) -> str:
30 """Return absolute path to a mesh file under data/mesh/."""
31 return os.path.join(
32 os.path.dirname(__file__), "..", "..", "data", "mesh", name
33 )
34
35
36def _build_fv(mpi, mesh) -> CFV.FiniteVolume:
37 """Construct a FiniteVolume with default settings on a given mesh."""
38 fv = CFV.FiniteVolume(mpi, mesh)
39 settings = fv.GetSettings()
40 settings["intOrder"] = 3
41 settings["maxOrder"] = 1
42 fv.ParseSettings(settings)
43
44 fv.SetCellAtrBasic()
45 fv.ConstructCellVolume()
46 fv.ConstructCellBary()
47 fv.ConstructCellCent()
48 fv.ConstructCellIntJacobiDet()
49 fv.ConstructCellIntPPhysics()
50 fv.ConstructCellAlignedHBox()
51 fv.ConstructCellMajorHBoxCoordInertia()
52
53 fv.SetFaceAtrBasic()
54 fv.ConstructFaceCent()
55 fv.ConstructFaceArea()
56 fv.ConstructFaceIntJacobiDet()
57 fv.ConstructFaceIntPPhysics()
58 fv.ConstructFaceUnitNorm()
59 fv.ConstructFaceMeanNorm()
60
61 fv.ConstructCellSmoothScale()
62 return fv
63
64
65@pytest.fixture(scope="module")
66def wall_mesh_fv(mpi):
67 """Build FiniteVolume on the Uniform_3x3_wall mesh (9 quads, [-1,2]^2)."""
68 mesh_file = _data_mesh_path("Uniform_3x3_wall.cgns")
69 mesh, reader, name2Id = create_mesh_from_CGNS(mesh_file, mpi, 2)
70 fv = _build_fv(mpi, mesh)
71 return mesh, fv, name2Id
72
73
74@pytest.fixture(scope="module")
76 """Build FiniteVolume on Uniform_3x3 with periodic BCs (9 quads, [0,3]^2)."""
77 mesh_file = _data_mesh_path("Uniform_3x3.cgns")
78 mesh, reader, name2Id = create_mesh_from_CGNS(
79 mesh_file,
80 mpi,
81 2,
82 periodic_geometry={
83 "translation1": [3, 0, 0],
84 "translation2": [0, 3, 0],
85 },
86 )
87 fv = _build_fv(mpi, mesh)
88 return mesh, fv, name2Id
89
90
91# ===================================================================
92# Cell volume tests
93# ===================================================================
94
95
97 """Verify cell volumes for uniform quad meshes."""
98
99 def test_wall_mesh_cell_volumes(self, wall_mesh_fv):
100 """Each cell in Uniform_3x3_wall has volume 1.0 (unit squares on [-1,2]^2)."""
101 mesh, fv, _ = wall_mesh_fv
102 for iCell in range(mesh.NumCell()):
103 vol = fv.GetCellVol(iCell)
104 assert vol == pytest.approx(1.0, abs=1e-12), (
105 f"Cell {iCell} volume = {vol}, expected 1.0"
106 )
107
108 def test_periodic_mesh_cell_volumes(self, periodic_mesh_fv):
109 """Each cell in Uniform_3x3 has volume 1.0 (unit squares on [0,3]^2)."""
110 mesh, fv, _ = periodic_mesh_fv
111 for iCell in range(mesh.NumCell()):
112 vol = fv.GetCellVol(iCell)
113 assert vol == pytest.approx(1.0, abs=1e-12), (
114 f"Cell {iCell} volume = {vol}, expected 1.0"
115 )
116
117
118# ===================================================================
119# Global volume tests
120# ===================================================================
121
122
124 """Verify global (summed) volume equals the domain area."""
125
126 def test_wall_mesh_global_volume(self, wall_mesh_fv):
127 """Domain [-1,2]^2 has area 9.0."""
128 _, fv, _ = wall_mesh_fv
129 assert fv.GetGlobalVol() == pytest.approx(9.0, abs=1e-10)
130
131 def test_periodic_mesh_global_volume(self, periodic_mesh_fv):
132 """Domain [0,3]^2 has area 9.0."""
133 _, fv, _ = periodic_mesh_fv
134 assert fv.GetGlobalVol() == pytest.approx(9.0, abs=1e-10)
135
136
137# ===================================================================
138# Face area tests
139# ===================================================================
140
141
143 """Verify face areas on uniform quad meshes are 1.0 (unit edge length)."""
144
145 def test_wall_mesh_face_areas(self, wall_mesh_fv):
146 mesh, fv, _ = wall_mesh_fv
147 for iFace in range(mesh.NumFaceProc()):
148 area = fv.GetFaceArea(iFace)
149 assert area == pytest.approx(1.0, abs=1e-12), (
150 f"Face {iFace} area = {area}, expected 1.0"
151 )
152
153 def test_periodic_mesh_face_areas(self, periodic_mesh_fv):
154 mesh, fv, _ = periodic_mesh_fv
155 for iFace in range(mesh.NumFaceProc()):
156 area = fv.GetFaceArea(iFace)
157 assert area == pytest.approx(1.0, abs=1e-12), (
158 f"Face {iFace} area = {area}, expected 1.0"
159 )
160
161
162# ===================================================================
163# Cell barycenter tests
164# ===================================================================
165
166
168 """Verify cell barycenters are at cell centers."""
169
171 """All barycenters should be within [-1,2]^2."""
172 mesh, fv, _ = wall_mesh_fv
173 for iCell in range(mesh.NumCell()):
174 bary = fv.GetCellBary(iCell)
175 assert -1.0 - 1e-10 <= bary[0] <= 2.0 + 1e-10, (
176 f"Cell {iCell} bary x = {bary[0]} out of range"
177 )
178 assert -1.0 - 1e-10 <= bary[1] <= 2.0 + 1e-10, (
179 f"Cell {iCell} bary y = {bary[1]} out of range"
180 )
181
183 """Barycenters of unit-square cells are at half-integer offsets from -1."""
184 mesh, fv, _ = wall_mesh_fv
185 expected_coords = {-0.5, 0.5, 1.5}
186 bary_xs = set()
187 bary_ys = set()
188 for iCell in range(mesh.NumCell()):
189 bary = fv.GetCellBary(iCell)
190 bary_xs.add(round(bary[0], 8))
191 bary_ys.add(round(bary[1], 8))
192 assert bary_xs == expected_coords, f"Got x coords: {bary_xs}"
193 assert bary_ys == expected_coords, f"Got y coords: {bary_ys}"
194
195
196# ===================================================================
197# DOF array building
198# ===================================================================
199
200
202 """Verify DOF arrays can be built and have correct sizes."""
203
204 def test_build_udof_dynamic(self, wall_mesh_fv):
205 mesh, fv, _ = wall_mesh_fv
206 u = CFV.tUDof_D()
207 fv.BuildUDof_D(u, 1)
208 assert u.Size() >= mesh.NumCell()
209 assert u.father.Size() == mesh.NumCell()
210
211 def test_build_udof_fixed_5(self, wall_mesh_fv):
212 mesh, fv, _ = wall_mesh_fv
213 u = CFV.tUDof_5()
214 fv.BuildUDof_5(u, 5)
215 assert u.Size() >= mesh.NumCell()
216
217 def test_build_ugrad_3xD(self, wall_mesh_fv):
218 mesh, fv, _ = wall_mesh_fv
219 grad = CFV.tUGrad_3xD()
220 fv.BuildUGrad_3xD(grad, 1)
221 assert grad.Size() >= mesh.NumCell()
222
223 def test_udof_write_read(self, wall_mesh_fv):
224 """Writing and reading back DOF values through numpy views."""
225 mesh, fv, _ = wall_mesh_fv
226 u = CFV.tUDof_D()
227 fv.BuildUDof_D(u, 1)
228 for iCell in range(mesh.NumCell()):
229 u_i = np.array(u[iCell], copy=False)
230 u_i[0] = float(iCell) + 0.5
231 for iCell in range(mesh.NumCell()):
232 u_i = np.array(u[iCell], copy=False)
233 assert u_i[0] == pytest.approx(float(iCell) + 0.5, abs=1e-14)
234
235
236# ===================================================================
237# Array bytes reporting
238# ===================================================================
239
240
242 """Verify getArrayBytes returns a positive value."""
243
244 def test_array_bytes_positive(self, wall_mesh_fv):
245 _, fv, _ = wall_mesh_fv
246 nbytes = fv.getArrayBytes()
247 assert nbytes > 0
248
249
250# ===================================================================
251# Smooth scale ratio
252# ===================================================================
253
254
256 """Verify smooth scale ratio is finite and positive for all cells."""
257
258 def test_smooth_scale_ratio(self, wall_mesh_fv):
259 mesh, fv, _ = wall_mesh_fv
260 for iCell in range(mesh.NumCell()):
261 ratio = fv.GetCellSmoothScaleRatio(iCell)
262 assert np.isfinite(ratio), f"Cell {iCell} smooth scale ratio not finite"
263 assert ratio > 0, f"Cell {iCell} smooth scale ratio = {ratio}"
264
265
266# ===================================================================
267# Jacobi determinant at quadrature points
268# ===================================================================
269
270
272 """Verify Jacobian determinants at quadrature points are positive."""
273
274 def test_cell_jacobi_det_positive(self, wall_mesh_fv):
275 mesh, fv, _ = wall_mesh_fv
276 for iCell in range(mesh.NumCell()):
277 # iG=0 is the first quadrature point
278 jdet = fv.GetCellJacobiDet(iCell, 0)
279 assert jdet > 0, f"Cell {iCell} Jacobi det = {jdet}"
280
281 def test_face_jacobi_det_positive(self, wall_mesh_fv):
282 mesh, fv, _ = wall_mesh_fv
283 for iFace in range(mesh.NumFaceProc()):
284 jdet = fv.GetFaceJacobiDet(iFace, 0)
285 assert jdet > 0, f"Face {iFace} Jacobi det = {jdet}"
286
287
288if __name__ == "__main__":
289 pytest.main([__file__, "-v"])
test_wall_mesh_barycenters_are_half_integers(self, wall_mesh_fv)
test_periodic_mesh_cell_volumes(self, periodic_mesh_fv)
test_periodic_mesh_face_areas(self, periodic_mesh_fv)
test_periodic_mesh_global_volume(self, periodic_mesh_fv)
test_cell_jacobi_det_positive(self, wall_mesh_fv)
test_face_jacobi_det_positive(self, wall_mesh_fv)
str _data_mesh_path(str name)
CFV.FiniteVolume _build_fv(mpi, mesh)
set(LIBNAME cfv) set(LINKS) set(LINKS_SHARED geom_shared dnds_shared $
Definition CMakeLists.txt:5
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:215